Compare commits

..

31 Commits

Author SHA1 Message Date
Gregory Schier
c6666b9623 Update tauri and signing deps to try fixing windows signing 2025-09-21 08:40:43 -07:00
Gregory Schier
fa98351e30 Fix lint 2025-09-21 08:04:47 -07:00
Gregory Schier
3c8be3f5b9 Gen models 2025-09-21 08:01:49 -07:00
Gregory Schier
eb3d1c409b Merge pull request #256
* Update environment model to get ready for request/folder environments

* Folder environments in UI

* Folder environments working

* Tweaks and fixes

* Tweak environment encryption UX

* Tweak environment encryption UX

* Address comments

* Update fn name

* Add tsc back to lint rules

* Update src-web/components/EnvironmentEditor.tsx

* Merge remote-tracking branch 'origin/folder-environments' into folder…
2025-09-21 07:54:26 -07:00
Jhonatan Matías
46b049c72b Fix Typos (#255) 2025-09-18 10:40:32 -07:00
Hao Xiang
fec64b5c02 fix http response load when filter (#251) 2025-09-16 13:01:00 -07:00
dependabot[bot]
8c3ed60579 Bump vite-plugin-static-copy from 3.1.1 to 3.1.2 (#252)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-16 11:20:34 -07:00
moshyfawn
907e09a417 [Plugins] [CopyAsCURL] [Auth] [JWT] Include custom bearer prefix to copy CURL (#253)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-09-16 11:20:23 -07:00
dependabot[bot]
28c6af8f94 Bump vite from 7.0.4 to 7.0.7 (#254)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-16 10:25:34 -07:00
Gabriel Oliveira
f8b0510d08 feat(settings): add do not check for updates (#246)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-08-08 13:25:55 -07:00
Gregory Schier
5f99b7df05 Use logger for plugin logs so they actually show
https://feedback.yaak.app/p/log-statements-dont-appear-from-within-plugins
2025-08-08 13:00:38 -07:00
Gregory Schier
158877b355 Fix SVG viewer crashing 2025-08-08 12:59:59 -07:00
Gregory Schier
8b84545b67 Prevent curl copy from using stale body data
https://feedback.yaak.app/p/copy-as-curl-includes-wrong-data-property
2025-08-02 10:03:35 -07:00
Gregory Schier
0e28079965 Fix GraphQL introspection breaking app
https://feedback.yaak.app/p/workspace-crash-when-graphql-introspection-returns-unexpected-response
2025-08-02 09:58:53 -07:00
Gregory Schier
5d5f9cc943 Better iFrame sandboxing
https://feedback.yaak.app/p/completely-white-ui
2025-08-02 09:47:34 -07:00
Gregory Schier
b71bc2cc92 Fix gRPC/WS hang because of ALPN
https://feedback.yaak.app/p/grpc-stalls-at-inspecting-schema-no-timeout-no-manual-proto
2025-08-02 09:37:28 -07:00
Gregory Schier
23191dcfc3 Revert change 2025-07-27 08:54:57 -07:00
Gregory Schier
372b15689d Fix min-size on md code 2025-07-27 08:54:44 -07:00
Gregory Schier
5c6d6fb7e4 Switch to menu right more easily 2025-07-27 08:45:56 -07:00
Gregory Schier
835a2e93e9 Text selection and syntax highlighting to markdown previews
https://feedback.yaak.app/p/enable-text-selection-in-the-info-section
2025-07-27 08:33:44 -07:00
Gregory Schier
93c6f6d611 re-enable http/2 support 2025-07-27 07:38:12 -07:00
Gregory Schier
b445261b32 Some tweaks 2025-07-26 15:48:58 -07:00
Gregory Schier
685b59cee9 Fix error 2025-07-26 14:34:40 -07:00
Gregory Schier
38529cc89e Plugin init/dispose 2025-07-26 14:28:59 -07:00
Gregory Schier
0d98b95b61 Don't prompt for updates on Linux unless APPIMAGE env exists 2025-07-25 15:10:04 -07:00
Gregory Schier
e044dcae3e Add back Rose Pine themes 2025-07-25 09:39:37 -07:00
Gregory Schier
b5b7b1638d Use physical key codes for zoom hotkeys 2025-07-24 09:15:57 -07:00
Gregory Schier
9d6ac8a107 Add template.desktop to make icon work until we can upgrade Tauri CLI 2025-07-24 08:44:55 -07:00
Gregory Schier
6440df492e Generate icons 2025-07-24 08:07:16 -07:00
Gregory Schier
2cdd97cabb Fix header spacing for window controls
https://feedback.yaak.app/p/app-bar-icons-not-aligned-correctly-when-fullscreen
2025-07-24 07:57:38 -07:00
Gregory Schier
20681e5be3 Scoped OAuth 2 tokens 2025-07-23 22:03:03 -07:00
141 changed files with 2144 additions and 1121 deletions

View File

@@ -62,7 +62,7 @@ jobs:
- name: install dependencies (windows only)
if: matrix.platform == 'windows-latest'
run: cargo install --force trusted-signing-cli --version 0.5.0
run: cargo install --force trusted-signing-cli
- name: Install NPM Dependencies
run: npm ci

609
package-lock.json generated
View File

@@ -58,7 +58,7 @@
"@eslint/compat": "^1.3.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.29.0",
"@tauri-apps/cli": "2.4.1",
"@tauri-apps/cli": "^2.8.4",
"@typescript-eslint/eslint-plugin": "^8.27.0",
"@typescript-eslint/parser": "^8.27.0",
"@yaakapp/cli": "^0.2.7",
@@ -3081,9 +3081,9 @@
}
},
"node_modules/@tauri-apps/api": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.6.0.tgz",
"integrity": "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.8.0.tgz",
"integrity": "sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==",
"license": "Apache-2.0 OR MIT",
"funding": {
"type": "opencollective",
@@ -3091,9 +3091,9 @@
}
},
"node_modules/@tauri-apps/cli": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.4.1.tgz",
"integrity": "sha512-9Ta81jx9+57FhtU/mPIckDcOBtPTUdKM75t4+aA0X84b8Sclb0jy1xA8NplmcRzp2fsfIHNngU2NiRxsW5+yOQ==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.8.4.tgz",
"integrity": "sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g==",
"dev": true,
"license": "Apache-2.0 OR MIT",
"bin": {
@@ -3107,23 +3107,23 @@
"url": "https://opencollective.com/tauri"
},
"optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "2.4.1",
"@tauri-apps/cli-darwin-x64": "2.4.1",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.4.1",
"@tauri-apps/cli-linux-arm64-gnu": "2.4.1",
"@tauri-apps/cli-linux-arm64-musl": "2.4.1",
"@tauri-apps/cli-linux-riscv64-gnu": "2.4.1",
"@tauri-apps/cli-linux-x64-gnu": "2.4.1",
"@tauri-apps/cli-linux-x64-musl": "2.4.1",
"@tauri-apps/cli-win32-arm64-msvc": "2.4.1",
"@tauri-apps/cli-win32-ia32-msvc": "2.4.1",
"@tauri-apps/cli-win32-x64-msvc": "2.4.1"
"@tauri-apps/cli-darwin-arm64": "2.8.4",
"@tauri-apps/cli-darwin-x64": "2.8.4",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.8.4",
"@tauri-apps/cli-linux-arm64-gnu": "2.8.4",
"@tauri-apps/cli-linux-arm64-musl": "2.8.4",
"@tauri-apps/cli-linux-riscv64-gnu": "2.8.4",
"@tauri-apps/cli-linux-x64-gnu": "2.8.4",
"@tauri-apps/cli-linux-x64-musl": "2.8.4",
"@tauri-apps/cli-win32-arm64-msvc": "2.8.4",
"@tauri-apps/cli-win32-ia32-msvc": "2.8.4",
"@tauri-apps/cli-win32-x64-msvc": "2.8.4"
}
},
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.4.1.tgz",
"integrity": "sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.8.4.tgz",
"integrity": "sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA==",
"cpu": [
"arm64"
],
@@ -3138,9 +3138,9 @@
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.4.1.tgz",
"integrity": "sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.8.4.tgz",
"integrity": "sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g==",
"cpu": [
"x64"
],
@@ -3155,9 +3155,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.4.1.tgz",
"integrity": "sha512-9tDijkRB+CchAGjXxYdY9l/XzFpLp1yihUtGXJz9eh+3qIoRI043n3e+6xmU8ZURr7XPnu+R4sCmXs6HD+NCEQ==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.8.4.tgz",
"integrity": "sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw==",
"cpu": [
"arm"
],
@@ -3172,9 +3172,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.4.1.tgz",
"integrity": "sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.8.4.tgz",
"integrity": "sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw==",
"cpu": [
"arm64"
],
@@ -3189,9 +3189,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.1.tgz",
"integrity": "sha512-Hp0zXgeZNKmT+eoJSCxSBUm2QndNuRxR55tmIeNm3vbyUMJN/49uW7nurZ5fBPsacN4Pzwlx1dIMK+Gnr9A69w==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.8.4.tgz",
"integrity": "sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw==",
"cpu": [
"arm64"
],
@@ -3206,9 +3206,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.4.1.tgz",
"integrity": "sha512-3T3bo2E4fdYRvzcXheWUeQOVB+LunEEi92iPRgOyuSVexVE4cmHYl+MPJF+EUV28Et0hIVTsHibmDO0/04lAFg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.8.4.tgz",
"integrity": "sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ==",
"cpu": [
"riscv64"
],
@@ -3223,9 +3223,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.4.1.tgz",
"integrity": "sha512-kLN0FdNONO+2i+OpU9+mm6oTGufRC00e197TtwjpC0N6K2K8130w7Q3FeODIM2CMyg0ov3tH+QWqKW7GNhHFzg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.8.4.tgz",
"integrity": "sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw==",
"cpu": [
"x64"
],
@@ -3240,9 +3240,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.1.tgz",
"integrity": "sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.8.4.tgz",
"integrity": "sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA==",
"cpu": [
"x64"
],
@@ -3257,9 +3257,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.4.1.tgz",
"integrity": "sha512-4JFrslsMCJQG1c573T9uqQSAbF3j/tMKkMWzsIssv8jvPiP++OG61A2/F+y9te9/Q/O95cKhDK63kaiO5xQaeg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.8.4.tgz",
"integrity": "sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w==",
"cpu": [
"arm64"
],
@@ -3274,9 +3274,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.4.1.tgz",
"integrity": "sha512-9eXfFORehYSCRwxg2KodfmX/mhr50CI7wyBYGbPLePCjr5z0jK/9IyW6r0tC+ZVjwpX48dkk7hKiUgI25jHjzA==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.8.4.tgz",
"integrity": "sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA==",
"cpu": [
"ia32"
],
@@ -3291,9 +3291,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.4.1.tgz",
"integrity": "sha512-60a4Ov7Jrwqz2hzDltlS7301dhSAmM9dxo+IRBD3xz7yobKrgaHXYpWvnRomYItHcDd51VaKc9292H8/eE/gsw==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.8.4.tgz",
"integrity": "sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA==",
"cpu": [
"x64"
],
@@ -3317,57 +3317,57 @@
}
},
"node_modules/@tauri-apps/plugin-dialog": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.3.0.tgz",
"integrity": "sha512-ylSBvYYShpGlKKh732ZuaHyJ5Ie1JR71QCXewCtsRLqGdc8Is4xWdz6t43rzXyvkItM9syNPMvFVcvjgEy+/GA==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.0.tgz",
"integrity": "sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-fs": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.0.tgz",
"integrity": "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.2.tgz",
"integrity": "sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-log": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.6.0.tgz",
"integrity": "sha512-gVp3l31akA1Jk2bZsTA0hMFD5/gLe49Nw1btu5lViau0QqgC2XyT79LSwvy7a44ewtQbSexchqIg7oTJKMIbXQ==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.7.0.tgz",
"integrity": "sha512-81XQ2f93x4vmIB5OY0XlYAxy60cHdYLs0Ki8Qp38tNATRiuBit+Orh3frpY3qfYQnqEvYVyRub7YRJWlmW2RRA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-opener": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.4.0.tgz",
"integrity": "sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.0.tgz",
"integrity": "sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-os": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.3.0.tgz",
"integrity": "sha512-dm3bDsMuUngpIQdJ1jaMkMfyQpHyDcaTIKTFaAMHoKeUd+Is3UHO2uzhElr6ZZkfytIIyQtSVnCWdW2Kc58f3g==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.3.1.tgz",
"integrity": "sha512-ty5V8XDUIFbSnrk3zsFoP3kzN+vAufYzalJSlmrVhQTImIZa1aL1a03bOaP2vuBvfR+WDRC6NgV2xBl8G07d+w==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-shell": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.0.tgz",
"integrity": "sha512-6GIRxO2z64uxPX4CCTuhQzefvCC0ew7HjdBhMALiGw74vFBDY95VWueAHOHgNOMV4UOUAFupyidN9YulTe5xlA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.1.tgz",
"integrity": "sha512-jjs2WGDO/9z2pjNlydY/F5yYhNsscv99K5lCmU5uKjsVvQ3dRlDhhtVYoa4OLDmktLtQvgvbQjCFibMl6tgGfw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@types/babel__core": {
@@ -3619,6 +3619,16 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/shell-quote": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.5.tgz",
@@ -9018,6 +9028,16 @@
"node": ">= 0.4"
}
},
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@@ -9081,6 +9101,71 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
"integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
"license": "MIT",
"dependencies": {
"@types/hast": "^2.0.0",
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript/node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
"integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^2"
}
},
"node_modules/hastscript/node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"license": "MIT"
},
"node_modules/hastscript/node_modules/comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/hastscript/node_modules/property-information": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
"integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/hastscript/node_modules/space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/hexy": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz",
@@ -9093,6 +9178,21 @@
"node": ">=10.4"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/highlightjs-vue": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
"license": "CC0-1.0"
},
"node_modules/history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
@@ -10782,6 +10882,33 @@
"tslib": "^2.0.3"
}
},
"node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lowlight/node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -14103,6 +14230,15 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -14339,6 +14475,23 @@
"node": ">=0.10.0"
}
},
"node_modules/react-syntax-highlighter": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"refractor": "^3.6.0"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/react-universal-interface": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",
@@ -14654,6 +14807,122 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/refractor": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
"license": "MIT",
"dependencies": {
"hastscript": "^6.0.0",
"parse-entities": "^2.0.0",
"prismjs": "~1.27.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"license": "MIT",
"dependencies": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/refractor/node_modules/prismjs": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
"integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/reftools": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz",
@@ -17595,21 +17864,24 @@
}
},
"node_modules/vite": {
"version": "6.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.7.tgz",
"integrity": "sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==",
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.7.tgz",
"integrity": "sha512-hc6LujN/EkJHmxeiDJMs0qBontZ1cdBvvoCbWhVjzUFTU329VRyOC46gHNSA8NcOC5yzCeXpwI40tieI3DEZqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
"fdir": "^6.4.6",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.40.0",
"tinyglobby": "^0.2.14"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -17618,14 +17890,14 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "*",
"less": "^4.0.0",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
@@ -17690,9 +17962,9 @@
}
},
"node_modules/vite-plugin-static-copy": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.1.tgz",
"integrity": "sha512-oR53SkL5cX4KT1t18E/xU50vJDo0N8oaHza4EMk0Fm+2/u6nQivxavOfrDk3udWj+dizRizB/QnBvJOOQrTTAQ==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.2.tgz",
"integrity": "sha512-aVmYOzptLVOI2b1jL+cmkF7O6uhRv1u5fvOkQgbohWZp2CbR22kn9ZqkCUIt9umKF7UhdbsEpshn1rf4720QFg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17749,6 +18021,37 @@
"vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7"
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/vitest": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
@@ -18263,7 +18566,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4"
@@ -18750,14 +19052,14 @@
"@tanstack/react-query": "^5.76.1",
"@tanstack/react-router": "^1.120.3",
"@tanstack/react-virtual": "^3.13.8",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
"@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-log": "^2.4.0",
"@tauri-apps/plugin-opener": "^2.2.7",
"@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-shell": "^2.2.1",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-log": "^2.7.0",
"@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.1",
"buffer": "^6.0.3",
"classnames": "^2.5.1",
"cm6-graphql": "^0.2.1",
@@ -18784,6 +19086,7 @@
"react-dom": "^19.1.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
"react-syntax-highlighter": "^15.6.1",
"react-use": "^17.6.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
@@ -18803,6 +19106,7 @@
"@types/parse-color": "^1.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0",
@@ -18813,28 +19117,13 @@
"postcss": "^8.5.6",
"postcss-nesting": "^13.0.2",
"tailwindcss": "^3.4.17",
"vite": "^7.0.4",
"vite-plugin-static-copy": "^3.1.1",
"vite": "^7.0.7",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-svgr": "^4.3.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.5.0"
}
},
"src-web/node_modules/fdir": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"src-web/node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
@@ -18845,19 +19134,6 @@
"jiti": "bin/jiti.js"
}
},
"src-web/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"src-web/node_modules/postcss-nested": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
@@ -18961,81 +19237,6 @@
"bin": {
"uuid": "dist/esm/bin/uuid"
}
},
"src-web/node_modules/vite": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.4.tgz",
"integrity": "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.6",
"picomatch": "^4.0.2",
"postcss": "^8.5.6",
"rollup": "^4.40.0",
"tinyglobby": "^0.2.14"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
}
}
}

View File

@@ -57,6 +57,9 @@
"migration": "node scripts/create-migration.cjs",
"build": "npm run --workspaces --if-present build",
"build-plugins": "npm run --workspaces --if-present build",
"icons": "run-p icons:*",
"icons:dev": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
"icons:release": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
"bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
@@ -74,7 +77,7 @@
"@eslint/compat": "^1.3.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.29.0",
"@tauri-apps/cli": "2.4.1",
"@tauri-apps/cli": "^2.8.4",
"@typescript-eslint/eslint-plugin": "^8.27.0",
"@typescript-eslint/parser": "^8.27.0",
"@yaakapp/cli": "^0.2.7",

View File

@@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js";
export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, };
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, };
@@ -387,7 +385,7 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type JsonPrimitive = string | number | boolean | null;
@@ -419,6 +417,8 @@ required?: boolean, };
export type PromptTextResponse = { value: string | null, };
export type ReloadResponse = { silent: boolean, };
export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, };
export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, };

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };

View File

@@ -61,4 +61,7 @@ export interface Context {
templates: {
render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>;
};
plugin: {
reload(): void;
};
}

View File

@@ -6,12 +6,16 @@ import type { ImporterPlugin } from './ImporterPlugin';
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import type { ThemePlugin } from './ThemePlugin';
export type { Context } from './Context';
import type { Context } from './Context';
export type { Context };
/**
* The global structure of a Yaak plugin
*/
export type PluginDefinition = {
init?: (ctx: Context) => void | Promise<void>;
dispose?: () => void | Promise<void>;
importer?: ImporterPlugin;
themes?: ThemePlugin[];
filter?: FilterPlugin;

View File

@@ -21,7 +21,7 @@ export class PluginHandle {
this.#instance.postMessage(event);
}
terminate() {
this.#instance.terminate();
async terminate() {
await this.#instance.terminate();
}
}

View File

@@ -1,6 +1,5 @@
import {
BootRequest,
BootResponse,
DeleteKeyValueResponse,
FindHttpResponsesResponse,
FormInput,
@@ -56,19 +55,19 @@ export class PluginInstance {
// Reload plugin if the JS or package.json changes
const windowContextNone: PluginWindowContext = { type: 'none' };
this.#mod = {};
this.#mod = {} as any;
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8'));
const bootResponse: BootResponse = {
name: this.#pkg.name ?? 'unknown',
version: this.#pkg.version ?? '0.0.1',
};
const fileChangeCallback = async () => {
await this.#mod?.dispose?.();
this.#importModule();
await this.#mod?.init?.(this.#newCtx({ type: 'none' }));
return this.#sendPayload(
windowContextNone,
{ type: 'reload_response', ...bootResponse },
{
type: 'reload_response',
silent: false,
},
null,
);
};
@@ -85,23 +84,20 @@ export class PluginInstance {
this.#appToPluginEvents.emit(event);
}
terminate() {
async terminate() {
await this.#mod?.dispose?.();
this.#unimportModule();
}
async #onMessage(event: InternalEvent) {
const ctx = this.#newCtx(event);
const ctx = this.#newCtx(event.windowContext);
const { windowContext, payload, id: replyId } = event;
try {
if (payload.type === 'boot_request') {
// console.log('Plugin initialized', pkg.name, { capabilities, enableWatch });
const payload: InternalEventPayload = {
type: 'boot_response',
name: this.#pkg.name ?? 'unknown',
version: this.#pkg.version ?? '0.0.1',
};
this.#sendPayload(windowContext, payload, replyId);
await this.#mod?.init?.(ctx);
this.#sendPayload(windowContext, { type: 'boot_response' }, replyId);
return;
}
@@ -109,6 +105,7 @@ export class PluginInstance {
const payload: InternalEventPayload = {
type: 'terminate_response',
};
await this.terminate();
this.#sendPayload(windowContext, payload, replyId);
return;
}
@@ -332,10 +329,6 @@ export class PluginInstance {
return;
}
}
if (payload.type === 'reload_request') {
this.#importModule();
}
} catch (err) {
const error = `${err}`.replace(/^Error:\s*/g, '');
console.log('Plugin call threw exception', payload.type, '→', error);
@@ -447,11 +440,11 @@ export class PluginInstance {
this.#sendEvent(eventToSend);
}
#newCtx(event: InternalEvent): Context {
#newCtx(windowContext: PluginWindowContext): Context {
return {
clipboard: {
copyText: async (text) => {
await this.#sendAndWaitForReply(event.windowContext, {
await this.#sendAndWaitForReply(windowContext, {
type: 'copy_text_request',
text,
});
@@ -459,7 +452,7 @@ export class PluginInstance {
},
toast: {
show: async (args) => {
await this.#sendAndWaitForReply(event.windowContext, {
await this.#sendAndWaitForReply(windowContext, {
type: 'show_toast_request',
...args,
});
@@ -476,21 +469,21 @@ export class PluginInstance {
onClose?.();
}
};
this.#sendAndListenForEvents(event.windowContext, payload, onEvent);
this.#sendAndListenForEvents(windowContext, payload, onEvent);
return {
close: () => {
const closePayload: InternalEventPayload = {
type: 'close_window_request',
label: args.label,
};
this.#sendPayload(event.windowContext, closePayload, null);
this.#sendPayload(windowContext, closePayload, null);
},
};
},
},
prompt: {
text: async (args) => {
const reply: PromptTextResponse = await this.#sendAndWaitForReply(event.windowContext, {
const reply: PromptTextResponse = await this.#sendAndWaitForReply(windowContext, {
type: 'prompt_text_request',
...args,
});
@@ -504,7 +497,7 @@ export class PluginInstance {
...args,
} as const;
const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>(
event.windowContext,
windowContext,
payload,
);
return httpResponses;
@@ -517,7 +510,7 @@ export class PluginInstance {
...args,
} as const;
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return grpcRequest;
@@ -530,7 +523,7 @@ export class PluginInstance {
...args,
} as const;
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
event.windowContext,
windowContext,
payload,
);
return httpRequest;
@@ -541,7 +534,7 @@ export class PluginInstance {
...args,
} as const;
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return httpResponse;
@@ -552,7 +545,7 @@ export class PluginInstance {
...args,
} as const;
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return httpRequest;
@@ -565,7 +558,7 @@ export class PluginInstance {
...args,
} as const;
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
event.windowContext,
windowContext,
payload,
);
return value;
@@ -573,7 +566,7 @@ export class PluginInstance {
listNames: async () => {
const payload = { type: 'list_cookie_names_request' } as const;
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
event.windowContext,
windowContext,
payload,
);
return names;
@@ -587,7 +580,7 @@ export class PluginInstance {
render: async (args) => {
const payload = { type: 'template_render_request', ...args } as const;
const result = await this.#sendAndWaitForReply<TemplateRenderResponse>(
event.windowContext,
windowContext,
payload,
);
return result.data as any;
@@ -597,7 +590,7 @@ export class PluginInstance {
get: async <T>(key: string) => {
const payload = { type: 'get_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(
event.windowContext,
windowContext,
payload,
);
return result.value ? (JSON.parse(result.value) as T) : undefined;
@@ -609,17 +602,22 @@ export class PluginInstance {
key,
value: valueStr,
};
await this.#sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
await this.#sendAndWaitForReply<GetKeyValueResponse>(windowContext, payload);
},
delete: async (key: string) => {
const payload = { type: 'delete_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(
event.windowContext,
windowContext,
payload,
);
return result.deleted;
},
},
plugin: {
reload: () => {
this.#sendPayload({ type: 'none' }, { type: 'reload_response', silent: true }, null);
},
},
};
}
}

View File

@@ -46,7 +46,7 @@ async function handleIncoming(msg: string) {
}
if (pluginEvent.payload.type === 'terminate_request') {
plugin.terminate();
await plugin.terminate();
console.log('Terminated plugin worker', pluginEvent.pluginRefId);
delete plugins[pluginEvent.pluginRefId];
}

View File

@@ -34,18 +34,18 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
let finalUrl = request.url || '';
const urlParams = (request.urlParameters ?? []).filter(onlyEnabled);
if (urlParams.length > 0) {
// Build url
// Build url
const [base, hash] = finalUrl.split('#');
const separator = base!.includes('?') ? '&' : '?';
const queryString = urlParams
.map(p => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
.join('&');
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
}
xs.push(quote(finalUrl));
xs.push(NEWLINE);
// Add headers
for (const h of (request.headers ?? []).filter(onlyEnabled)) {
xs.push('--header', quote(`${h.name}: ${h.value}`));
@@ -53,7 +53,11 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
}
// Add form params
if (Array.isArray(request.body?.form)) {
const type = request.bodyType ?? 'none';
if (
(type === 'multipart/form-data' || type === 'application/x-www-form-urlencoded') &&
Array.isArray(request.body?.form)
) {
const flag = request.bodyType === 'multipart/form-data' ? '--form' : '--data';
for (const p of (request.body?.form ?? []).filter(onlyEnabled)) {
if (p.file) {
@@ -65,14 +69,14 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
}
xs.push(NEWLINE);
}
} else if (typeof request.body?.query === 'string') {
} else if (type === 'graphql' && typeof request.body?.query === 'string') {
const body = {
query: request.body.query || '',
variables: maybeParseJSON(request.body.variables, undefined),
};
xs.push('--data', quote(JSON.stringify(body)));
xs.push(NEWLINE);
} else if (typeof request.body?.text === 'string') {
} else if (type !== 'none' && typeof request.body?.text === 'string') {
xs.push('--data', quote(request.body.text));
xs.push(NEWLINE);
}
@@ -89,7 +93,9 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
// Add bearer authentication
if (request.authenticationType === 'bearer') {
xs.push('--header', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
const value =
`${request.authentication?.prefix ?? 'Bearer'} ${request.authentication?.token ?? ''}`.trim();
xs.push('--header', quote(`Authorization: ${value}`));
xs.push(NEWLINE);
}
@@ -116,4 +122,4 @@ function maybeParseJSON<T>(v: string, fallback: T) {
} catch {
return fallback;
}
}
}

View File

@@ -12,9 +12,7 @@ describe('exporter-curl', () => {
{ name: 'c', value: 'ccc', enabled: false },
],
}),
).toEqual(
[`curl 'https://yaak.app/?a=aaa&b=bbb'`].join(` \\n `),
);
).toEqual([`curl 'https://yaak.app?a=aaa&b=bbb'`].join(` \\n `));
});
test('Exports GET with params and hash', async () => {
@@ -27,9 +25,7 @@ describe('exporter-curl', () => {
{ name: 'c', value: 'ccc', enabled: false },
],
}),
).toEqual(
[`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(` \\n `),
);
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(` \\n `));
});
test('Exports POST with url form data', async () => {
expect(
@@ -62,7 +58,10 @@ describe('exporter-curl', () => {
},
}),
).toEqual(
[`curl -X POST 'https://yaak.app'`, `--data '{"query":"{foo,bar}","variables":{"a":"aaa","b":"bbb"}}'`].join(` \\\n `),
[
`curl -X POST 'https://yaak.app'`,
`--data '{"query":"{foo,bar}","variables":{"a":"aaa","b":"bbb"}}'`,
].join(` \\\n `),
);
});
@@ -206,6 +205,34 @@ describe('exporter-curl', () => {
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer tok'`].join(` \\\n `));
});
test('Bearer auth with custom prefix', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
token: 'abc123',
prefix: 'Token',
},
}),
).toEqual(
[`curl 'https://yaak.app'`, `--header 'Authorization: Token abc123'`].join(` \\\n `),
);
});
test('Bearer auth with empty prefix', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
token: 'xyz789',
prefix: '',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: xyz789'`].join(` \\\n `));
});
test('Broken bearer auth', async () => {
expect(
await convertToCurl({
@@ -216,6 +243,18 @@ describe('exporter-curl', () => {
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer '`].join(` \\\n `));
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(` \\\n `));
});
});
test('Stale body data', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
bodyType: 'none',
body: {
text: 'ignore me',
},
}),
).toEqual([`curl 'https://yaak.app'`].join(` \\\n `));
});
});

View File

@@ -12,6 +12,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -12,6 +12,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -12,6 +12,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -12,6 +12,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -12,7 +12,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonwebtoken": "^9.0.2"

View File

@@ -12,6 +12,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -1,19 +0,0 @@
import type { Context } from '@yaakapp/api';
import type { AccessToken } from './store';
import { getToken } from './store';
export async function getAccessTokenIfNotExpired(
ctx: Context,
contextId: string,
): Promise<AccessToken | null> {
const token = await getToken(ctx, contextId);
if (token == null || isTokenExpired(token)) {
return null;
}
return token;
}
export function isTokenExpired(token: AccessToken) {
return token.expiresAt && Date.now() > token.expiresAt;
}

View File

@@ -1,12 +1,12 @@
import type { Context, HttpRequest } from '@yaakapp/api';
import { readFileSync } from 'node:fs';
import { isTokenExpired } from './getAccessTokenIfNotExpired';
import type { AccessToken, AccessTokenRawResponse } from './store';
import type { AccessToken, AccessTokenRawResponse, TokenStoreArgs } from './store';
import { deleteToken, getToken, storeToken } from './store';
import { isTokenExpired } from './util';
export async function getOrRefreshAccessToken(
ctx: Context,
contextId: string,
tokenArgs: TokenStoreArgs,
{
scope,
accessTokenUrl,
@@ -23,7 +23,7 @@ export async function getOrRefreshAccessToken(
forceRefresh?: boolean;
},
): Promise<AccessToken | null> {
const token = await getToken(ctx, contextId);
const token = await getToken(ctx, tokenArgs);
if (token == null) {
return null;
}
@@ -75,7 +75,7 @@ export async function getOrRefreshAccessToken(
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
// and returning null;
console.log('[oauth2] Unauthorized refresh_token request');
await deleteToken(ctx, contextId);
await deleteToken(ctx, tokenArgs);
return null;
}
@@ -108,5 +108,5 @@ export async function getOrRefreshAccessToken(
refresh_token: response.refresh_token ?? token.response.refresh_token,
};
return storeToken(ctx, contextId, newResponse);
return storeToken(ctx, tokenArgs, newResponse);
}

View File

@@ -2,7 +2,7 @@ import type { Context } from '@yaakapp/api';
import { createHash, randomBytes } from 'node:crypto';
import { fetchAccessToken } from '../fetchAccessToken';
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
import type { AccessToken } from '../store';
import type { AccessToken, TokenStoreArgs } from '../store';
import { getDataDirKey, storeToken } from '../store';
export const PKCE_SHA256 = 'S256';
@@ -41,7 +41,14 @@ export async function getAuthorizationCode(
tokenName: 'access_token' | 'id_token';
},
): Promise<AccessToken> {
const token = await getOrRefreshAccessToken(ctx, contextId, {
const tokenArgs: TokenStoreArgs = {
contextId,
clientId,
accessTokenUrl,
authorizationUrl: authorizationUrlRaw,
};
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
accessTokenUrl,
scope,
clientId,
@@ -128,7 +135,7 @@ export async function getAuthorizationCode(
],
});
return storeToken(ctx, contextId, response, tokenName);
return storeToken(ctx, tokenArgs, response, tokenName);
}
export function genPkceCodeVerifier() {

View File

@@ -1,7 +1,8 @@
import type { Context } from '@yaakapp/api';
import { fetchAccessToken } from '../fetchAccessToken';
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
import type { TokenStoreArgs } from '../store';
import { getToken, storeToken } from '../store';
import { isTokenExpired } from '../util';
export async function getClientCredentials(
ctx: Context,
@@ -22,7 +23,13 @@ export async function getClientCredentials(
credentialsInBody: boolean;
},
) {
const token = await getToken(ctx, contextId);
const tokenArgs: TokenStoreArgs = {
contextId,
clientId,
accessTokenUrl,
authorizationUrl: null,
};
const token = await getToken(ctx, tokenArgs);
if (token && !isTokenExpired(token)) {
return token;
}
@@ -38,5 +45,5 @@ export async function getClientCredentials(
params: [],
});
return storeToken(ctx, contextId, response);
return storeToken(ctx, tokenArgs, response);
}

View File

@@ -1,7 +1,7 @@
import type { Context } from '@yaakapp/api';
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
import type { AccessToken, AccessTokenRawResponse} from '../store';
import type { AccessToken, AccessTokenRawResponse } from '../store';
import { getToken, storeToken } from '../store';
import { isTokenExpired } from '../util';
export async function getImplicit(
ctx: Context,
@@ -26,7 +26,13 @@ export async function getImplicit(
tokenName: 'access_token' | 'id_token';
},
): Promise<AccessToken> {
const token = await getToken(ctx, contextId);
const tokenArgs = {
contextId,
clientId,
accessTokenUrl: null,
authorizationUrl: authorizationUrlRaw,
};
const token = await getToken(ctx, tokenArgs);
if (token != null && !isTokenExpired(token)) {
return token;
}
@@ -82,7 +88,7 @@ export async function getImplicit(
const response = Object.fromEntries(params) as unknown as AccessTokenRawResponse;
try {
resolve(storeToken(ctx, contextId, response));
resolve(storeToken(ctx, tokenArgs, response));
} catch (err) {
reject(err);
}

View File

@@ -1,7 +1,7 @@
import type { Context } from '@yaakapp/api';
import { fetchAccessToken } from '../fetchAccessToken';
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
import type { AccessToken} from '../store';
import type { AccessToken, TokenStoreArgs } from '../store';
import { storeToken } from '../store';
export async function getPassword(
@@ -27,7 +27,13 @@ export async function getPassword(
credentialsInBody: boolean;
},
): Promise<AccessToken> {
const token = await getOrRefreshAccessToken(ctx, contextId, {
const tokenArgs: TokenStoreArgs = {
contextId,
clientId,
accessTokenUrl,
authorizationUrl: null,
};
const token = await getOrRefreshAccessToken(ctx, tokenArgs, {
accessTokenUrl,
scope,
clientId,
@@ -52,5 +58,5 @@ export async function getPassword(
],
});
return storeToken(ctx, contextId, response);
return storeToken(ctx, tokenArgs, response);
}

View File

@@ -15,7 +15,7 @@ import {
import { getClientCredentials } from './grants/clientCredentials';
import { getImplicit } from './grants/implicit';
import { getPassword } from './grants/password';
import type { AccessToken } from './store';
import type { AccessToken, TokenStoreArgs } from './store';
import { deleteToken, getToken, resetDataDirKey } from './store';
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
@@ -83,8 +83,14 @@ export const plugin: PluginDefinition = {
actions: [
{
label: 'Copy Current Token',
async onSelect(ctx, { contextId }) {
const token = await getToken(ctx, contextId);
async onSelect(ctx, { contextId, values }) {
const tokenArgs: TokenStoreArgs = {
contextId,
authorizationUrl: stringArg(values, 'authorizationUrl'),
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
clientId: stringArg(values, 'clientId'),
};
const token = await getToken(ctx, tokenArgs);
if (token == null) {
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
} else {
@@ -99,8 +105,14 @@ export const plugin: PluginDefinition = {
},
{
label: 'Delete Token',
async onSelect(ctx, { contextId }) {
if (await deleteToken(ctx, contextId)) {
async onSelect(ctx, { contextId, values }) {
const tokenArgs: TokenStoreArgs = {
contextId,
authorizationUrl: stringArg(values, 'authorizationUrl'),
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
clientId: stringArg(values, 'clientId'),
};
if (await deleteToken(ctx, tokenArgs)) {
await ctx.toast.show({ message: 'Token deleted', color: 'success' });
} else {
await ctx.toast.show({ message: 'No token to delete', color: 'warning' });
@@ -281,8 +293,14 @@ export const plugin: PluginDefinition = {
{
type: 'accordion',
label: 'Access Token Response',
async dynamic(ctx, { contextId }) {
const token = await getToken(ctx, contextId);
async dynamic(ctx, { contextId, values }) {
const tokenArgs: TokenStoreArgs = {
contextId,
authorizationUrl: stringArg(values, 'authorizationUrl'),
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
clientId: stringArg(values, 'clientId'),
};
const token = await getToken(ctx, tokenArgs);
if (token == null) {
return { hidden: true };
}
@@ -316,9 +334,10 @@ export const plugin: PluginDefinition = {
accessTokenUrl === '' || accessTokenUrl.match(/^https?:\/\//)
? accessTokenUrl
: `https://${accessTokenUrl}`,
authorizationUrl: authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
? authorizationUrl
: `https://${authorizationUrl}`,
authorizationUrl:
authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
? authorizationUrl
: `https://${authorizationUrl}`,
clientId: stringArg(values, 'clientId'),
clientSecret: stringArg(values, 'clientSecret'),
redirectUri: stringArgOrNull(values, 'redirectUri'),

View File

@@ -1,8 +1,9 @@
import type { Context } from '@yaakapp/api';
import { createHash } from 'node:crypto';
export async function storeToken(
ctx: Context,
contextId: string,
args: TokenStoreArgs,
response: AccessTokenRawResponse,
tokenName: 'access_token' | 'id_token' = 'access_token',
) {
@@ -15,16 +16,16 @@ export async function storeToken(
response,
expiresAt,
};
await ctx.store.set<AccessToken>(tokenStoreKey(contextId), token);
await ctx.store.set<AccessToken>(tokenStoreKey(args), token);
return token;
}
export async function getToken(ctx: Context, contextId: string) {
return ctx.store.get<AccessToken>(tokenStoreKey(contextId));
export async function getToken(ctx: Context, args: TokenStoreArgs) {
return ctx.store.get<AccessToken>(tokenStoreKey(args));
}
export async function deleteToken(ctx: Context, contextId: string) {
return ctx.store.delete(tokenStoreKey(contextId));
export async function deleteToken(ctx: Context, args: TokenStoreArgs) {
return ctx.store.delete(tokenStoreKey(args));
}
export async function resetDataDirKey(ctx: Context, contextId: string) {
@@ -37,8 +38,25 @@ export async function getDataDirKey(ctx: Context, contextId: string) {
return `${contextId}::${key}`;
}
function tokenStoreKey(contextId: string) {
return ['token', contextId].join('::');
export interface TokenStoreArgs {
contextId: string;
clientId: string;
accessTokenUrl: string | null;
authorizationUrl: string | null;
}
/**
* Generate a store key to use based on some arguments. The arguments will be normalized a bit to
* account for slight variations (like domains with and without a protocol scheme).
*/
function tokenStoreKey(args: TokenStoreArgs) {
const hash = createHash('md5');
if (args.contextId) hash.update(args.contextId.trim());
if (args.clientId) hash.update(args.clientId.trim());
if (args.accessTokenUrl) hash.update(args.accessTokenUrl.trim().replace(/^https?:\/\//, ''));
if (args.authorizationUrl) hash.update(args.authorizationUrl.trim().replace(/^https?:\/\//, ''));
const key = hash.digest('hex');
return ['token', key].join('::');
}
function dataDirStoreKey(contextId: string) {

View File

@@ -0,0 +1,5 @@
import type { AccessToken } from './store';
export function isTokenExpired(token: AccessToken) {
return token.expiresAt && Date.now() > token.expiresAt;
}

View File

@@ -12,7 +12,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"shell-quote": "^1.8.1"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"yaml": "^2.4.2"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"openapi-to-postmanv2": "^5.0.0",

View File

@@ -8,6 +8,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0"

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"jsonpath-plus": "^10.3.0",

View File

@@ -5,7 +5,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"date-fns": "^4.1.0"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"uuid": "^11.1.0"

View File

@@ -7,7 +7,7 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",

View File

@@ -7,6 +7,6 @@
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"lint": "eslint . --ext .ts,.tsx"
"lint":"eslint . --ext .ts,.tsx"
}
}

View File

@@ -595,5 +595,112 @@ export const plugin: PluginDefinition = {
danger: 'hsl(343,81%,75%)',
},
},
{
id: 'rose-pine',
label: 'Rosé Pine',
dark: true,
base: {
surface: 'hsl(249,22%,12%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(199,49%,60%)',
success: 'hsl(180,43%,73%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(1,74%,79%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,23%,15%)',
},
sidebar: {
surface: 'hsl(247,23%,15%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,66%)',
textSubtlest: 'hsl(249,12%,52%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,33%)',
},
},
},
{
id: 'rose-pine-moon',
label: 'Rosé Pine Moon',
dark: true,
base: {
surface: 'hsl(246,24%,17%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(248,15%,61%)',
info: 'hsl(197,48%,60%)',
success: 'hsl(197,48%,60%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(2,66%,75%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,24%,20%)',
},
sidebar: {
surface: 'hsl(247,24%,20%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,55%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,31%)',
},
},
},
{
id: 'rose-pine-dawn',
label: 'Rosé Pine Dawn',
dark: false,
base: {
surface: 'hsl(32,57%,95%)',
border: 'hsl(10,9%,86%)',
surfaceHighlight: 'hsl(25,35%,93%)',
text: 'hsl(248,19%,40%)',
textSubtle: 'hsl(248,12%,52%)',
textSubtlest: 'hsl(257,9%,61%)',
primary: 'hsl(271,27%,56%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(197,52%,36%)',
success: 'hsl(188,31%,45%)',
notice: 'hsl(34,64%,49%)',
warning: 'hsl(2,47%,64%)',
danger: 'hsl(343,35%,55%)',
},
components: {
responsePane: {
border: 'hsl(20,12%,90%)',
},
sidebar: {
border: 'hsl(20,12%,90%)',
},
appHeader: {
border: 'hsl(20,12%,90%)',
},
input: {
border: 'hsl(10,9%,86%)',
},
dialog: {
border: 'hsl(20,12%,90%)',
},
menu: {
surface: 'hsl(28,40%,92%)',
border: 'hsl(10,9%,86%)',
},
},
},
],
};

218
src-tauri/Cargo.lock generated
View File

@@ -736,7 +736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
dependencies = [
"serde",
"toml",
"toml 0.8.23",
]
[[package]]
@@ -822,6 +822,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "charset"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e"
dependencies = [
"base64 0.22.1",
"encoding_rs",
]
[[package]]
name = "chrono"
version = "0.4.41"
@@ -1350,9 +1360,9 @@ dependencies = [
[[package]]
name = "dlopen2"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6"
checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff"
dependencies = [
"dlopen2_derive",
"libc",
@@ -1446,7 +1456,7 @@ dependencies = [
"cc",
"memchr",
"rustc_version",
"toml",
"toml 0.8.23",
"vswhom",
"winreg",
]
@@ -3619,6 +3629,16 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-javascript-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a"
dependencies = [
"objc2 0.6.1",
"objc2-core-foundation",
]
[[package]]
name = "objc2-metal"
version = "0.2.2"
@@ -3667,6 +3687,17 @@ dependencies = [
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-security"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44"
dependencies = [
"bitflags 2.9.1",
"objc2 0.6.1",
"objc2-core-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.1"
@@ -3691,6 +3722,8 @@ dependencies = [
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.1",
"objc2-javascript-core",
"objc2-security",
]
[[package]]
@@ -5223,6 +5256,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -5280,9 +5322,9 @@ dependencies = [
[[package]]
name = "serialize-to-javascript"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
dependencies = [
"serde",
"serde_json",
@@ -5291,13 +5333,13 @@ dependencies = [
[[package]]
name = "serialize-to-javascript-impl"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.101",
]
[[package]]
@@ -5616,17 +5658,18 @@ dependencies = [
"cfg-expr",
"heck 0.5.0",
"pkg-config",
"toml",
"toml 0.8.23",
"version-compare",
]
[[package]]
name = "tao"
version = "0.34.0"
version = "0.34.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7"
dependencies = [
"bitflags 2.9.1",
"block2 0.6.1",
"core-foundation 0.10.1",
"core-graphics 0.24.0",
"crossbeam-channel",
@@ -5695,12 +5738,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.6.2"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c"
dependencies = [
"anyhow",
"bytes",
"cookie",
"dirs",
"dunce",
"embed_plist",
@@ -5719,6 +5763,7 @@ dependencies = [
"objc2-app-kit",
"objc2-foundation 0.3.1",
"objc2-ui-kit",
"objc2-web-kit",
"percent-encoding",
"plist",
"raw-window-handle",
@@ -5746,9 +5791,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.3.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f"
dependencies = [
"anyhow",
"cargo_toml",
@@ -5762,15 +5807,15 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
"toml",
"toml 0.9.5",
"walkdir",
]
[[package]]
name = "tauri-codegen"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -5795,9 +5840,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -5809,9 +5854,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3"
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
dependencies = [
"anyhow",
"glob",
@@ -5820,7 +5865,7 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
"toml",
"toml 0.9.5",
"walkdir",
]
@@ -5841,11 +5886,12 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
version = "2.4.0"
version = "2.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab261eb006db10ab478e3fbb5a4e2692df3f7eb3e28300ee2b64428979167ed0"
checksum = "cd67112fb1131834c2a7398ffcba520dbbf62c17de3b10329acd1a3554b1a9bb"
dependencies = [
"dunce",
"plist",
"rust-ini",
"serde",
"serde_json",
@@ -5861,9 +5907,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28"
checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e"
dependencies = [
"log",
"raw-window-handle",
@@ -5879,9 +5925,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.4.0"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f"
checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
dependencies = [
"anyhow",
"dunce",
@@ -5895,15 +5941,15 @@ dependencies = [
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.12",
"toml",
"toml 0.9.5",
"url",
]
[[package]]
name = "tauri-plugin-log"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a59139183e0907cec1499dddee4e085f5a801dc659efa0848ee224f461371426"
checksum = "61c1438bc7662acd16d508c919b3c087efd63669a4c75625dff829b1c75975ec"
dependencies = [
"android_logger",
"byte-unit",
@@ -5923,9 +5969,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-opener"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321"
checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5"
dependencies = [
"dunce",
"glob",
@@ -5945,9 +5991,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-os"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05bccb4c6de4299beec5a9b070878a01bce9e2c945aa7a75bcea38bcba4c675d"
checksum = "77a1c77ebf6f20417ab2a74e8c310820ba52151406d0c80fbcea7df232e3f6ba"
dependencies = [
"gethostname 1.0.2",
"log",
@@ -5963,9 +6009,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-shell"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25"
checksum = "54777d0c0d8add34eea3ced84378619ef5b97996bd967d3038c668feefd21071"
dependencies = [
"encoding_rs",
"log",
@@ -5984,9 +6030,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-single-instance"
version = "2.3.0"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b441b6d5d1a194e9fee0b358fe0d602ded845d0f580e1f8c8ef78ebc3c8b225d"
checksum = "fb9cac815bf11c4a80fb498666bcdad66d65b89e3ae24669e47806febb76389c"
dependencies = [
"serde",
"serde_json",
@@ -6032,9 +6078,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-window-state"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3d22b21b9cec73601b512a868f7c74f93c044d44fd6ca1c84e9d6afb6b1559"
checksum = "2d5f6fe3291bfa609c7e0b0ee3bedac294d94c7018934086ce782c1d0f2a468e"
dependencies = [
"bitflags 2.9.1",
"log",
@@ -6047,9 +6093,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.7.0"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
dependencies = [
"cookie",
"dpi",
@@ -6058,20 +6104,23 @@ dependencies = [
"jni",
"objc2 0.6.1",
"objc2-ui-kit",
"objc2-web-kit",
"raw-window-handle",
"serde",
"serde_json",
"tauri-utils",
"thiserror 2.0.12",
"url",
"webkit2gtk",
"webview2-com",
"windows",
]
[[package]]
name = "tauri-runtime-wry"
version = "2.7.1"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
dependencies = [
"gtk",
"http",
@@ -6096,9 +6145,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.5.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
dependencies = [
"anyhow",
"brotli",
@@ -6125,7 +6174,7 @@ dependencies = [
"serde_with",
"swift-rs",
"thiserror 2.0.12",
"toml",
"toml 0.9.5",
"url",
"urlpattern",
"uuid",
@@ -6140,7 +6189,7 @@ checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4"
dependencies = [
"embed-resource",
"indexmap 2.9.0",
"toml",
"toml 0.8.23",
]
[[package]]
@@ -6390,11 +6439,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
]
[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"indexmap 2.9.0",
"serde",
"serde_spanned 1.0.0",
"toml_datetime 0.7.0",
"toml_parser",
"toml_writer",
"winnow 0.7.10",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
@@ -6404,6 +6468,15 @@ dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.15"
@@ -6411,7 +6484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.9.0",
"toml_datetime",
"toml_datetime 0.6.11",
"winnow 0.5.40",
]
@@ -6422,7 +6495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap 2.9.0",
"toml_datetime",
"toml_datetime 0.6.11",
"winnow 0.5.40",
]
@@ -6434,18 +6507,33 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.9.0",
"serde",
"serde_spanned",
"toml_datetime",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_write",
"winnow 0.7.10",
]
[[package]]
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow 0.7.10",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
[[package]]
name = "tonic"
version = "0.12.3"
@@ -7723,14 +7811,15 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "wry"
version = "0.52.1"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9"
checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90"
dependencies = [
"base64 0.22.1",
"block2 0.6.1",
"cookie",
"crossbeam-channel",
"dirs",
"dpi",
"dunce",
"gdkx11",
@@ -7826,6 +7915,7 @@ dependencies = [
name = "yaak-app"
version = "0.0.0"
dependencies = [
"charset",
"chrono",
"cookie",
"encoding_rs",
@@ -8143,9 +8233,9 @@ dependencies = [
[[package]]
name = "zbus"
version = "5.7.1"
version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68"
checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7"
dependencies = [
"async-broadcast",
"async-executor",
@@ -8168,7 +8258,7 @@ dependencies = [
"tokio",
"tracing",
"uds_windows",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
"winnow 0.7.10",
"zbus_macros",
"zbus_names",
@@ -8177,9 +8267,9 @@ dependencies = [
[[package]]
name = "zbus_macros"
version = "5.7.1"
version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a"
checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca"
dependencies = [
"proc-macro-crate 3.3.0",
"proc-macro2",

View File

@@ -34,7 +34,7 @@ strip = true # Automatically strip symbols from the binary.
cargo-clippy = []
[build-dependencies]
tauri-build = { version = "2.2.0", features = [] }
tauri-build = { version = "2.4.1", features = [] }
[target.'cfg(target_os = "linux")'.dependencies]
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
@@ -49,22 +49,22 @@ log = "0.4.27"
md5 = "0.8.0"
mime_guess = "2.0.5"
rand = "0.9.0"
reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider", "socks"] }
reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider", "socks", "http2"] }
reqwest_cookie_store = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = "2.3.0"
tauri-plugin-dialog = { workspace = true }
tauri-plugin-fs = "2.4.0"
tauri-plugin-log = { version = "2.6.0", features = ["colored"] }
tauri-plugin-opener = "2.4.0"
tauri-plugin-os = "2.3.0"
tauri-plugin-fs = "2.4.2"
tauri-plugin-log = { version = "2.7.0", features = ["colored"] }
tauri-plugin-opener = "2.5.0"
tauri-plugin-os = "2.3.1"
tauri-plugin-shell = { workspace = true }
tauri-plugin-deep-link = "2.4.0"
tauri-plugin-single-instance = { version = "2.3.0", features = ["deep-link"] }
tauri-plugin-deep-link = "2.4.3"
tauri-plugin-single-instance = { version = "2.3.4", features = ["deep-link"] }
tauri-plugin-updater = "2.9.0"
tauri-plugin-window-state = "2.3.0"
tauri-plugin-window-state = "2.4.0"
thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
tokio-stream = "0.1.17"
@@ -83,6 +83,7 @@ yaak-sse = { workspace = true }
yaak-sync = { workspace = true }
yaak-templates = { workspace = true }
yaak-ws = { path = "yaak-ws" }
charset = "0.1.5"
[workspace.dependencies]
chrono = "0.4.41"
@@ -90,10 +91,10 @@ hex = "0.4.3"
reqwest = "0.12.20"
serde = "1.0.219"
serde_json = "1.0.140"
tauri = "2.6.2"
tauri-plugin = "2.3.0"
tauri-plugin-dialog = "2.3.0"
tauri-plugin-shell = "2.3.0"
tauri = "2.8.5"
tauri-plugin = "2.4.0"
tauri-plugin-dialog = "2.4.0"
tauri-plugin-shell = "2.3.1"
tokio = "1.45.1"
thiserror = "2.0.12"
ts-rs = "11.0.1"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -1,16 +1,27 @@
use encoding_rs::SHIFT_JIS;
use log::debug;
use mime_guess::{Mime, mime};
use std::path::Path;
use std::str::FromStr;
use tokio::fs;
use yaak_models::models::HttpResponse;
pub async fn read_response_body<'a>(
response: HttpResponse,
) -> Option<String> {
let body_path = match response.body_path {
None => return None,
Some(p) => p,
};
pub async fn read_response_body(body_path: impl AsRef<Path>, content_type: &str) -> Option<String> {
let body = fs::read(body_path).await.ok()?;
let body_charset = parse_charset(content_type).unwrap_or("utf-8".to_string());
debug!("body_charset: {}", body_charset);
if let Some(decoder) = charset::Charset::for_label(body_charset.as_bytes()) {
debug!("Using decoder for charset: {}", body_charset);
let (cow, real_encoding, exist_replace) = decoder.decode(&body);
debug!(
"Decoded body with charset: {}, real_encoding: {:?}, exist_replace: {}",
body_charset, real_encoding, exist_replace
);
return cow.into_owned().into();
}
let body = fs::read(body_path).await.unwrap();
let (s, _, _) = SHIFT_JIS.decode(body.as_slice());
Some(s.to_string())
Some(String::from_utf8_lossy(&body).to_string())
}
fn parse_charset(content_type: &str) -> Option<String> {
let mime: Mime = Mime::from_str(content_type).ok()?;
mime.get_param(mime::CHARSET).map(|v| v.to_string())
}

View File

@@ -43,23 +43,18 @@ pub async fn send_http_request<R: Runtime>(
) -> Result<HttpResponse> {
let app_handle = window.app_handle().clone();
let plugin_manager = app_handle.state::<PluginManager>();
let (settings, workspace) = {
let db = window.db();
let settings = db.get_settings();
let workspace = db.get_workspace(&unrendered_request.workspace_id)?;
(settings, workspace)
};
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let settings = window.db().get_settings();
let workspace = window.db().get_workspace(&unrendered_request.workspace_id)?;
let environment_id = environment.map(|e| e.id);
let environment_chain = window.db().resolve_environments(
&unrendered_request.workspace_id,
unrendered_request.folder_id.as_deref(),
environment_id.as_deref(),
)?;
let response_id = og_response.id.clone();
let response = Arc::new(Mutex::new(og_response.clone()));
let cb = PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(window),
RenderPurpose::Send,
);
let update_source = UpdateSource::from_window(window);
let (resolved_request, auth_context_id) = match resolve_http_request(window, unrendered_request)
@@ -75,20 +70,23 @@ pub async fn send_http_request<R: Runtime>(
}
};
let request =
match render_http_request(&resolved_request, &base_environment, environment.as_ref(), &cb)
.await
{
Ok(r) => r,
Err(e) => {
return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
));
}
};
let cb = PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(window),
RenderPurpose::Send,
);
let request = match render_http_request(&resolved_request, environment_chain, &cb).await {
Ok(r) => r,
Err(e) => {
return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
));
}
};
let mut url_string = request.url.clone();
@@ -110,7 +108,7 @@ pub async fn send_http_request<R: Runtime>(
.referer(false)
.tls_info(true);
let tls_config = yaak_http::tls::get_config(workspace.setting_validate_certificates);
let tls_config = yaak_http::tls::get_config(workspace.setting_validate_certificates, true);
client_builder = client_builder.use_preconfigured_tls(tls_config);
match settings.proxy {

View File

@@ -30,12 +30,19 @@ use yaak_common::window::WorkspaceWindowTrait;
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
use yaak_grpc::{Code, ServiceDefinition, deserialize_message, serialize_message};
use yaak_models::models::{
CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType,
GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, Workspace, WorkspaceMeta,
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, Workspace,
WorkspaceMeta,
};
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
use yaak_plugins::events::{CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest};
use yaak_plugins::events::{
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_meta::PluginMetadata;
use yaak_plugins::template_callback::PluginTemplateCallback;
@@ -104,15 +111,11 @@ async fn cmd_render_template<R: Runtime>(
workspace_id: &str,
environment_id: Option<&str>,
) -> YaakResult<String> {
let environment = match environment_id {
Some(id) => app_handle.db().get_environment(id).ok(),
None => None,
};
let base_environment = app_handle.db().get_base_environment(&workspace_id)?;
let environment_chain =
app_handle.db().resolve_environments(workspace_id, None, environment_id)?;
let result = render_template(
template,
&base_environment,
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -141,21 +144,19 @@ async fn cmd_grpc_reflect<R: Runtime>(
app_handle: AppHandle<R>,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> YaakResult<Vec<ServiceDefinition>> {
let environment = match environment_id {
Some(id) => app_handle.db().get_environment(id).ok(),
None => None,
};
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
let (resolved_request, auth_context_id) = resolve_grpc_request(&window, &unrendered_request)?;
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let environment_chain = app_handle.db().resolve_environments(
&unrendered_request.workspace_id,
unrendered_request.folder_id.as_deref(),
environment_id,
)?;
let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?;
let req = render_grpc_request(
&resolved_request,
&base_environment,
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -190,20 +191,18 @@ async fn cmd_grpc_go<R: Runtime>(
window: WebviewWindow<R>,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> YaakResult<String> {
let environment = match environment_id {
Some(id) => app_handle.db().get_environment(id).ok(),
None => None,
};
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
let (resolved_request, auth_context_id) = resolve_grpc_request(&window, &unrendered_request)?;
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let environment_chain = app_handle.db().resolve_environments(
&unrendered_request.workspace_id,
unrendered_request.folder_id.as_deref(),
environment_id,
)?;
let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?;
let request = render_grpc_request(
&resolved_request,
&base_environment,
environment.as_ref(),
environment_chain.clone(),
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -294,9 +293,8 @@ async fn cmd_grpc_go<R: Runtime>(
let cb = {
let cancelled_rx = cancelled_rx.clone();
let app_handle = app_handle.clone();
let environment_chain = environment_chain.clone();
let window = window.clone();
let base_environment = base_environment.clone();
let environment = environment.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
@@ -321,12 +319,12 @@ async fn cmd_grpc_go<R: Runtime>(
let app_handle = app_handle.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let environment_chain = environment_chain.clone();
let msg = block_in_place(|| {
tauri::async_runtime::block_on(async {
render_template(
msg.as_str(),
&base_environment,
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -390,12 +388,12 @@ async fn cmd_grpc_go<R: Runtime>(
let window = window.clone();
let app_handle = app_handle.clone();
let base_event = base_msg.clone();
let environment_chain = environment_chain.clone();
let req = request.clone();
let msg = if req.message.is_empty() { "{}".to_string() } else { req.message };
let msg = render_template(
msg.as_str(),
&base_environment.clone(),
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -720,33 +718,43 @@ async fn cmd_format_json(text: &str) -> YaakResult<String> {
}
#[tauri::command]
async fn cmd_filter_response<R: Runtime>(
async fn cmd_http_response_body<R: Runtime>(
window: WebviewWindow<R>,
app_handle: AppHandle<R>,
response_id: &str,
plugin_manager: State<'_, PluginManager>,
filter: &str,
response_id: &str,
filter: Option<&str>,
) -> YaakResult<FilterResponse> {
let response = app_handle.db().get_http_response(response_id)?;
if let None = response.body_path {
return Err(GenericError("Response body path not set".to_string()));
}
let mut content_type = "".to_string();
for header in response.headers.iter() {
if header.name.to_lowercase() == "content-type" {
content_type = header.value.to_string().to_lowercase();
break;
let body_path = match response.body_path {
None => {
return Err(GenericError("Response body path not set".to_string()));
}
}
Some(p) => p,
};
let body = read_response_body(response)
let content_type = response
.headers
.iter()
.find_map(|h| {
if h.name.eq_ignore_ascii_case("content-type") { Some(h.value.as_str()) } else { None }
})
.unwrap_or_default();
let body = read_response_body(&body_path, content_type)
.await
.ok_or(GenericError("Failed to find response body".to_string()))?;
// TODO: Have plugins register their own content type (regex?)
Ok(plugin_manager.filter_data(&window, filter, &body, &content_type).await?)
match filter {
Some(filter) if !filter.is_empty() => {
Ok(plugin_manager.filter_data(&window, filter, &body, content_type).await?)
}
_ => Ok(FilterResponse {
content: body,
error: None,
}),
}
}
#[tauri::command]
@@ -817,10 +825,25 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
plugin_manager: State<'_, PluginManager>,
auth_name: &str,
values: HashMap<String, JsonPrimitive>,
request_id: &str,
request: AnyModel,
environment_id: Option<&str>,
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
let (workspace_id, folder_id) = match request.clone() {
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::Folder(m) => (m.workspace_id, m.folder_id),
AnyModel::Workspace(m) => (m.id, None),
m => {
return Err(GenericError(format!("Unsupported model to call auth config {m:?}")));
},
};
let environment_chain =
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
Ok(plugin_manager
.get_http_authentication_config(&window, auth_name, values, request_id)
.get_http_authentication_config(&window, environment_chain, auth_name, values, request.id())
.await?)
}
@@ -871,10 +894,30 @@ async fn cmd_call_http_authentication_action<R: Runtime>(
auth_name: &str,
action_index: i32,
values: HashMap<String, JsonPrimitive>,
model_id: &str,
model: AnyModel,
environment_id: Option<&str>,
) -> YaakResult<()> {
let (workspace_id, folder_id) = match model.clone() {
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
AnyModel::Folder(m) => (m.workspace_id, m.folder_id),
AnyModel::Workspace(m) => (m.id, None),
m => {
return Err(GenericError(format!("Unsupported model to call auth {m:?}")));
}
};
let environment_chain =
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
Ok(plugin_manager
.call_http_authentication_action(&window, auth_name, action_index, values, model_id)
.call_http_authentication_action(
&window,
environment_chain,
auth_name,
action_index,
values,
&model.id(),
)
.await?)
}
@@ -1238,7 +1281,10 @@ pub fn run() {
let _ = app_handle.emit(
"show_toast",
ShowToastRequest {
message: format!("Error handling deep link: {}", e.to_string()),
message: format!(
"Error handling deep link: {}",
e.to_string()
),
color: Some(Color::Danger),
icon: None,
},
@@ -1280,7 +1326,7 @@ pub fn run() {
cmd_delete_send_history,
cmd_dismiss_notification,
cmd_export_data,
cmd_filter_response,
cmd_http_response_body,
cmd_format_json,
cmd_get_http_authentication_summaries,
cmd_get_http_authentication_config,
@@ -1339,12 +1385,15 @@ pub fn run() {
} => {
let w = app_handle.get_webview_window(&label).unwrap();
let h = app_handle.clone();
// Run update check whenever the window is focused
tauri::async_runtime::spawn(async move {
let val: State<'_, Mutex<YaakUpdater>> = h.state();
let update_mode = get_update_mode(&w).await.unwrap();
if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await {
warn!("Failed to check for updates {e:?}");
if w.db().get_settings().autoupdate {
let val: State<'_, Mutex<YaakUpdater>> = h.state();
let update_mode = get_update_mode(&w).await.unwrap();
if let Err(e) = val.lock().await.maybe_check(&w, update_mode).await {
warn!("Failed to check for updates {e:?}");
};
};
});

View File

@@ -74,20 +74,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment = environment_from_window(&window);
let base_environment = app_handle
let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window
.db()
.get_base_environment(&workspace.id)
.expect("Failed to get base environment");
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let grpc_request = render_grpc_request(
&req.grpc_request,
&base_environment,
environment.as_ref(),
&cb,
)
.await
.expect("Failed to render grpc request");
let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb)
.await
.expect("Failed to render grpc request");
Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
grpc_request,
}))
@@ -98,20 +93,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment = environment_from_window(&window);
let base_environment = app_handle
let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window
.db()
.get_base_environment(&workspace.id)
.expect("Failed to get base environment");
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let http_request = render_http_request(
&req.http_request,
&base_environment,
environment.as_ref(),
&cb,
)
.await
.expect("Failed to render http request");
let http_request = render_http_request(&req.http_request, environment_chain, &cb)
.await
.expect("Failed to render http request");
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
http_request,
}))
@@ -122,13 +112,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment = environment_from_window(&window);
let base_environment = app_handle
let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window
.db()
.get_base_environment(&workspace.id)
.expect("Failed to get base environment");
.resolve_environments(&workspace.id, None, environment_id.as_deref())
.expect("Failed to resolve environments");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let data = render_json_value(req.data, &base_environment, environment.as_ref(), &cb)
let data = render_json_value(req.data, environment_chain, &cb)
.await
.expect("Failed to render template");
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
@@ -150,7 +140,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
None
}
InternalEventPayload::ReloadResponse(r) => {
InternalEventPayload::ReloadResponse(req) => {
let plugins = app_handle.db().list_plugins().unwrap();
for plugin in plugins {
if plugin.directory != plugin_handle.dir {
@@ -163,16 +153,20 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
};
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
}
let toast_event = plugin_handle.build_event_to_send(
&window_context,
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!("Reloaded plugin {}@{}", r.name, r.version),
icon: Some(Icon::Info),
..Default::default()
}),
None,
);
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
if !req.silent {
let info = plugin_handle.info();
let toast_event = plugin_handle.build_event_to_send(
&window_context,
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!("Reloaded plugin {}@{}", info.name, info.version),
icon: Some(Icon::Info),
..Default::default()
}),
None,
);
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
}
None
}
InternalEventPayload::SendHttpRequestRequest(req) => {

View File

@@ -5,35 +5,32 @@ use yaak_models::models::{
Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
};
use yaak_models::render::make_vars_hashmap;
use yaak_templates::{parse_and_render, render_json_value_raw, TemplateCallback};
use yaak_templates::{TemplateCallback, parse_and_render, render_json_value_raw};
pub async fn render_template<T: TemplateCallback>(
template: &str,
base_environment: &Environment,
environment: Option<&Environment>,
environment_chain: Vec<Environment>,
cb: &T,
) -> yaak_templates::error::Result<String> {
let vars = &make_vars_hashmap(base_environment, environment);
let vars = &make_vars_hashmap(environment_chain);
render(template, vars, cb).await
}
pub async fn render_json_value<T: TemplateCallback>(
value: Value,
base_environment: &Environment,
environment: Option<&Environment>,
environment_chain: Vec<Environment>,
cb: &T,
) -> yaak_templates::error::Result<Value> {
let vars = &make_vars_hashmap(base_environment, environment);
let vars = &make_vars_hashmap(environment_chain);
render_json_value_raw(value, vars, cb).await
}
pub async fn render_grpc_request<T: TemplateCallback>(
r: &GrpcRequest,
base_environment: &Environment,
environment: Option<&Environment>,
environment_chain: Vec<Environment>,
cb: &T,
) -> yaak_templates::error::Result<GrpcRequest> {
let vars = &make_vars_hashmap(base_environment, environment);
let vars = &make_vars_hashmap(environment_chain);
let mut metadata = Vec::new();
for p in r.metadata.clone() {
@@ -62,11 +59,10 @@ pub async fn render_grpc_request<T: TemplateCallback>(
pub async fn render_http_request<T: TemplateCallback>(
r: &HttpRequest,
base_environment: &Environment,
environment: Option<&Environment>,
environment_chain: Vec<Environment>,
cb: &T,
) -> yaak_templates::error::Result<HttpRequest> {
let vars = &make_vars_hashmap(base_environment, environment);
let vars = &make_vars_hashmap(environment_chain);
let mut url_parameters = Vec::new();
for p in r.url_parameters.clone() {

View File

@@ -66,6 +66,14 @@ impl YaakUpdater {
mode: UpdateMode,
update_trigger: UpdateTrigger,
) -> Result<bool> {
// Only AppImage supports updates on Linux, so skip if it's not
#[cfg(target_os = "linux")]
{
if std::env::var("APPIMAGE").is_err() {
return Ok(false);
}
}
let settings = window.db().get_settings();
let update_key = format!("{:x}", md5::compute(settings.id));
self.last_update_check = SystemTime::now();

View File

@@ -1,3 +1,13 @@
{
"productName": "yaak"
"productName": "yaak",
"bundle": {
"linux": {
"deb": {
"desktopTemplate": "./template.desktop"
},
"rpm": {
"desktopTemplate": "./template.desktop"
}
}
}
}

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Categories={{categories}}
Comment={{comment}}
Exec={{exec}}
Icon={{icon}}
Name={{name}}
StartupWMClass={{exec}}
Terminal=false
Type=Application

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };

View File

@@ -24,7 +24,7 @@ pub(crate) fn find_ssh_key() -> Option<PathBuf> {
None
}
pub(crate) fn get_current_branch(repo: &Repository) -> Result<Option<Branch>> {
pub(crate) fn get_current_branch(repo: &Repository) -> Result<Option<Branch<'_>>> {
for b in repo.branches(None)? {
let branch = b?.0;
if branch.is_head() {
@@ -101,7 +101,7 @@ pub(crate) fn get_default_remote_in_repo(repo: &Repository) -> Result<String> {
return Ok(DEFAULT_REMOTE_NAME.into());
}
// if only one remote exists pick that
// if only one remote exists, pick that
if remotes.len() == 1 {
let first_remote = remotes
.iter()

View File

@@ -4,8 +4,11 @@ use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
use tonic::body::BoxBody;
// I think ALPN breaks this because we're specifying http2_only
const WITH_ALPN: bool = false;
pub(crate) fn get_transport(validate_certificates: bool) -> Client<HttpsConnector<HttpConnector>, BoxBody> {
let tls_config = yaak_http::tls::get_config(validate_certificates);
let tls_config = yaak_http::tls::get_config(validate_certificates, WITH_ALPN);
let mut http = HttpConnector::new();
http.enforce_http(false);

View File

@@ -5,12 +5,12 @@ use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme};
use rustls_platform_verifier::BuilderVerifierExt;
use std::sync::Arc;
pub fn get_config(validate_certificates: bool) -> ClientConfig {
pub fn get_config(validate_certificates: bool, with_alpn: bool) -> ClientConfig {
let arc_crypto_provider = Arc::new(ring::default_provider());
let config_builder = ClientConfig::builder_with_provider(arc_crypto_provider)
.with_safe_default_protocol_versions()
.unwrap();
if validate_certificates {
let mut client = if validate_certificates {
// Use platform-native verifier to validate certificates
config_builder.with_platform_verifier().unwrap().with_no_client_auth()
} else {
@@ -18,7 +18,13 @@ pub fn get_config(validate_certificates: bool) -> ClientConfig {
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth()
};
if with_alpn {
client.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
}
client
}
// Copied from reqwest: https://github.com/seanmonstar/reqwest/blob/595c80b1fbcdab73ac2ae93e4edc3406f453df25/src/tls.rs#L608

View File

@@ -14,7 +14,7 @@ export type EditorKeymap = "default" | "vim" | "vscode" | "emacs";
export type EncryptedKey = { encryptedKey: string, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
export type ProxySettingAuth = { user: string, password: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, autoupdate: boolean, };
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };

View File

@@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN autoupdate BOOLEAN DEFAULT true NOT NULL;

View File

@@ -0,0 +1,62 @@
-- Create temporary table for migration
CREATE TABLE environments__new
(
id TEXT NOT NULL PRIMARY KEY,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted_at DATETIME,
workspace_id TEXT NOT NULL
REFERENCES workspaces ON DELETE CASCADE,
name TEXT NOT NULL,
variables TEXT DEFAULT '[]' NOT NULL,
model TEXT DEFAULT 'environment',
public BOOLEAN DEFAULT FALSE,
color TEXT,
-- NEW
parent_model TEXT DEFAULT 'workspace' NOT NULL,
parent_id TEXT
);
-- Backfill the data from the old table
-- - base=1 -> (workspace, NULL)
-- - base=0 -> (environment, id_of_workspace_base) (fallback to workspace,NULL if none)
INSERT INTO environments__new
(id, created_at, updated_at, deleted_at, workspace_id, name, variables, model, public, color, parent_model, parent_id)
SELECT
e.id,
e.created_at,
e.updated_at,
e.deleted_at,
e.workspace_id,
e.name,
e.variables,
e.model,
e.public,
e.color,
CASE
WHEN e.base = 1 THEN 'workspace'
WHEN (
SELECT COUNT(1)
FROM environments b
WHERE b.workspace_id = e.workspace_id AND b.base = 1
) > 0 THEN 'environment'
ELSE 'workspace'
END AS parent_model,
CASE
WHEN e.base = 1 THEN NULL
ELSE (
SELECT b.id
FROM environments b
WHERE b.workspace_id = e.workspace_id AND b.base = 1
ORDER BY b.created_at ASC, b.id ASC
LIMIT 1
)
END AS parent_id
FROM environments e;
-- Move everything to the new table
DROP TABLE environments;
ALTER TABLE environments__new
RENAME TO environments;

View File

@@ -89,7 +89,7 @@ impl<'a> DbContext<'a> {
col: impl IntoColumnRef,
value: impl Into<SimpleExpr>,
limit: Option<u64>,
) -> crate::error::Result<Vec<M>>
) -> Result<Vec<M>>
where
M: Into<AnyModel> + Clone + UpsertModelInfo,
{

View File

@@ -29,6 +29,9 @@ pub enum Error {
#[error("Multiple base environments for {0}. Delete duplicates before continuing.")]
MultipleBaseEnvironments(String),
#[error("Multiple folder environments for {0}. Delete duplicates before continuing.")]
MultipleFolderEnvironments(String),
#[error("unknown error")]
Unknown,

View File

@@ -120,6 +120,7 @@ pub struct Settings {
pub theme_dark: String,
pub theme_light: String,
pub update_channel: String,
pub autoupdate: bool,
}
impl UpsertModelInfo for Settings {
@@ -168,6 +169,7 @@ impl UpsertModelInfo for Settings {
(ThemeDark, self.theme_dark.as_str().into()),
(ThemeLight, self.theme_light.as_str().into()),
(UpdateChannel, self.update_channel.into()),
(Autoupdate, self.autoupdate.into()),
(ColoredMethods, self.colored_methods.into()),
(Proxy, proxy.into()),
])
@@ -190,6 +192,7 @@ impl UpsertModelInfo for Settings {
SettingsIden::ThemeDark,
SettingsIden::ThemeLight,
SettingsIden::UpdateChannel,
SettingsIden::Autoupdate,
SettingsIden::ColoredMethods,
]
}
@@ -219,6 +222,7 @@ impl UpsertModelInfo for Settings {
theme_light: row.get("theme_light")?,
hide_window_controls: row.get("hide_window_controls")?,
update_channel: row.get("update_channel")?,
autoupdate: row.get("autoupdate")?,
colored_methods: row.get("colored_methods")?,
})
}
@@ -529,9 +533,10 @@ pub struct Environment {
pub name: String,
pub public: bool,
pub base: bool,
pub variables: Vec<EnvironmentVariable>,
pub color: Option<String>,
pub parent_model: String,
pub parent_id: Option<String>,
}
impl UpsertModelInfo for Environment {
@@ -564,7 +569,8 @@ impl UpsertModelInfo for Environment {
(CreatedAt, upsert_date(source, self.created_at)),
(UpdatedAt, upsert_date(source, self.updated_at)),
(WorkspaceId, self.workspace_id.into()),
(Base, self.base.into()),
(ParentId, self.parent_id.into()),
(ParentModel, self.parent_model.into()),
(Color, self.color.into()),
(Name, self.name.trim().into()),
(Public, self.public.into()),
@@ -575,7 +581,8 @@ impl UpsertModelInfo for Environment {
fn update_columns() -> Vec<impl IntoIden> {
vec![
EnvironmentIden::UpdatedAt,
EnvironmentIden::Base,
EnvironmentIden::ParentId,
EnvironmentIden::ParentModel,
EnvironmentIden::Color,
EnvironmentIden::Name,
EnvironmentIden::Public,
@@ -594,7 +601,8 @@ impl UpsertModelInfo for Environment {
workspace_id: row.get("workspace_id")?,
created_at: row.get("created_at")?,
updated_at: row.get("updated_at")?,
base: row.get("base")?,
parent_id: row.get("parent_id")?,
parent_model: row.get("parent_model")?,
color: row.get("color")?,
name: row.get("name")?,
public: row.get("public")?,
@@ -2068,6 +2076,17 @@ macro_rules! define_any_model {
)*
}
impl AnyModel {
#[inline]
pub fn id(&self) -> &str {
match self {
$(
AnyModel::$type(inner) => &inner.id,
)*
}
}
}
$(
impl From<$type> for AnyModel {
fn from(value: $type) -> Self {

View File

@@ -1,5 +1,7 @@
use crate::db_context::DbContext;
use crate::error::Error::{MissingBaseEnvironment, MultipleBaseEnvironments};
use crate::error::Error::{
MissingBaseEnvironment, MultipleBaseEnvironments, MultipleFolderEnvironments,
};
use crate::error::Result;
use crate::models::{Environment, EnvironmentIden, EnvironmentVariable};
use crate::util::UpdateSource;
@@ -10,21 +12,31 @@ impl<'a> DbContext<'a> {
self.find_one(EnvironmentIden::Id, id)
}
pub fn get_environment_by_folder_id(&self, folder_id: &str) -> Result<Option<Environment>> {
let environments: Vec<Environment> =
self.find_many(EnvironmentIden::ParentId, folder_id, None)?;
if environments.len() > 1 {
return Err(MultipleFolderEnvironments(folder_id.to_string()));
}
Ok(environments.get(0).cloned())
}
pub fn get_base_environment(&self, workspace_id: &str) -> Result<Environment> {
let environments = self.list_environments_ensure_base(workspace_id)?;
let base_environments =
environments.into_iter().filter(|e| e.base).collect::<Vec<Environment>>();
let base_environments = environments
.into_iter()
.filter(|e| e.parent_id.is_none())
.collect::<Vec<Environment>>();
if base_environments.len() > 1 {
return Err(MultipleBaseEnvironments(workspace_id.to_string()));
}
let base_environment = base_environments.into_iter().find(|e| e.base).ok_or(
Ok(base_environments.first().cloned().ok_or(
// Should never happen because one should be created above if it does not exist
MissingBaseEnvironment(workspace_id.to_string()),
)?;
Ok(base_environment)
)?)
}
/// Lists environments and will create a base environment if one doesn't exist
@@ -32,13 +44,12 @@ impl<'a> DbContext<'a> {
let mut environments =
self.find_many::<Environment>(EnvironmentIden::WorkspaceId, workspace_id, None)?;
let base_environment = environments.iter().find(|e| e.base);
let base_environment = environments.iter().find(|e| e.parent_id.is_none());
if let None = base_environment {
let e = self.upsert_environment(
&Environment {
workspace_id: workspace_id.to_string(),
base: true,
name: "Global Variables".to_string(),
..Default::default()
},
@@ -98,4 +109,43 @@ impl<'a> DbContext<'a> {
source,
)
}
pub fn resolve_environments(
&self,
workspace_id: &str,
folder_id: Option<&str>,
active_environment_id: Option<&str>,
) -> Result<Vec<Environment>> {
let mut environments = Vec::new();
if let Some(folder_id) = folder_id {
let folder = self.get_folder(folder_id)?;
// Add current folder's environment
if let Some(e) = self.get_environment_by_folder_id(folder_id)? {
environments.push(e);
};
// Recurse up
let ancestors = self.resolve_environments(
workspace_id,
folder.folder_id.as_deref(),
active_environment_id,
)?;
environments.extend(ancestors);
} else {
// Add active and base environments
if let Some(id) = active_environment_id {
if let Ok(e) = self.get_environment(&id) {
// Add active sub environment
environments.push(e);
};
};
// Add the base environment
environments.push(self.get_base_environment(workspace_id)?);
}
Ok(environments)
}
}

View File

@@ -1,10 +1,7 @@
use crate::connection_or_tx::ConnectionOrTx;
use crate::db_context::DbContext;
use crate::error::Result;
use crate::models::{
Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader,
HttpRequestIden, WebsocketRequest, WebsocketRequestIden,
};
use crate::models::{Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader, HttpRequestIden, WebsocketRequest, WebsocketRequestIden};
use crate::util::UpdateSource;
use serde_json::Value;
use std::collections::BTreeMap;
@@ -37,6 +34,10 @@ impl<'a> DbContext<'a> {
self.delete_websocket_request(&m, source)?;
}
for e in self.find_many(EnvironmentIden::ParentId, fid, None)? {
self.delete_environment(&e, source)?;
}
// Recurse down into child folders
for folder in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
self.delete_folder(&folder, source)?;
@@ -99,6 +100,17 @@ impl<'a> DbContext<'a> {
)?;
}
for m in self.find_many::<Environment>(EnvironmentIden::ParentId, fid, None)? {
self.upsert_environment(
&Environment {
id: "".into(),
parent_id: Some(new_folder.id.clone()),
..m
},
source,
)?;
}
for m in self.find_many::<Folder>(FolderIden::FolderId, fid, None)? {
// Recurse down
self.duplicate_folder(

View File

@@ -31,6 +31,7 @@ impl<'a> DbContext<'a> {
theme_dark: "yaak-dark".to_string(),
theme_light: "yaak-light".to_string(),
update_channel: "stable".to_string(),
autoupdate: true,
colored_methods: false,
};
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")

View File

@@ -64,7 +64,7 @@ impl QueryManager {
}
}
pub fn connect(&self) -> DbContext {
pub fn connect(&self) -> DbContext<'_> {
let conn = self
.pool
.lock()

View File

@@ -1,14 +1,10 @@
use std::collections::HashMap;
use crate::models::{Environment, EnvironmentVariable};
use std::collections::HashMap;
pub fn make_vars_hashmap(
base_environment: &Environment,
environment: Option<&Environment>,
) -> HashMap<String, String> {
pub fn make_vars_hashmap(environment_chain: Vec<Environment>) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &base_environment.variables);
if let Some(e) = environment {
for e in environment_chain.iter().rev() {
variables = add_variable_to_map(variables, &e.variables);
}
@@ -31,4 +27,3 @@ fn add_variable_to_map(
map
}

View File

@@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js";
export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, };
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, };
@@ -387,7 +385,7 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type JsonPrimitive = string | number | boolean | null;
@@ -419,6 +417,8 @@ required?: boolean, };
export type PromptTextResponse = { value: string | null, };
export type ReloadResponse = { silent: boolean, };
export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, };
export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, };

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };

View File

@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use tauri::{Runtime, WebviewWindow};
use ts_rs::TS;
@@ -64,10 +65,9 @@ impl PluginWindowContext {
#[ts(export, export_to = "gen_events.ts")]
pub enum InternalEventPayload {
BootRequest(BootRequest),
BootResponse(BootResponse),
BootResponse,
ReloadRequest(EmptyPayload),
ReloadResponse(BootResponse),
ReloadResponse(ReloadResponse),
TerminateRequest,
TerminateResponse,
@@ -161,6 +161,17 @@ pub enum InternalEventPayload {
ErrorResponse(ErrorResponse),
}
impl InternalEventPayload {
pub fn type_name(&self) -> String {
if let Ok(Value::Object(map)) = serde_json::to_value(self) {
map.get("type").map(|s| s.as_str().unwrap_or("unknown").to_string())
} else {
None
}
.unwrap_or("invalid_event".to_string())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default)]
#[ts(export, type = "{}", export_to = "gen_events.ts")]
@@ -184,9 +195,8 @@ pub struct BootRequest {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct BootResponse {
pub name: String,
pub version: String,
pub struct ReloadResponse {
pub silent: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

View File

@@ -18,7 +18,9 @@ use crate::native_template_functions::template_function_secure;
use crate::nodejs::start_nodejs_plugin_runtime;
use crate::plugin_handle::PluginHandle;
use crate::server_ws::PluginRuntimeServerWebsocket;
use crate::template_callback::PluginTemplateCallback;
use log::{error, info, warn};
use serde_json::json;
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};
@@ -28,12 +30,16 @@ use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager, Runtime, WebviewWindow, is_dev};
use tokio::fs::read_dir;
use tokio::net::TcpListener;
use tokio::sync::mpsc::error::TrySendError;
use tokio::sync::{Mutex, mpsc};
use tokio::time::{Instant, timeout};
use yaak_models::models::Environment;
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::render::make_vars_hashmap;
use yaak_models::util::generate_id;
use yaak_templates::error::Error::RenderError;
use yaak_templates::error::Result as TemplateResult;
use yaak_templates::render_json_value_raw;
#[derive(Clone)]
pub struct PluginManager {
@@ -86,7 +92,14 @@ impl PluginManager {
while let Some(event) = events_rx.recv().await {
for (tx_id, tx) in subscribers.lock().await.iter_mut() {
if let Err(e) = tx.try_send(event.clone()) {
warn!("Failed to send event to subscriber {tx_id} {e:?}");
match e {
TrySendError::Full(e) => {
error!("Failed to send event to full subscriber {tx_id} {e:?}");
}
TrySendError::Closed(_) => {
// Subscriber already unsubscribed
}
}
}
}
}
@@ -192,7 +205,12 @@ impl PluginManager {
plugin: &PluginHandle,
) -> Result<()> {
// Terminate the plugin
plugin.terminate(window_context).await?;
self.send_to_plugin_and_wait(
window_context,
plugin,
&InternalEventPayload::TerminateRequest,
)
.await?;
// Remove the plugin from the list
let mut plugins = self.plugins.lock().await;
@@ -235,17 +253,14 @@ impl PluginManager {
)
.await??;
let mut plugins = self.plugins.lock().await;
if !matches!(event.payload, InternalEventPayload::BootResponse) {
return Err(UnknownEventErr);
}
// Remove the existing plugin (if exists) before adding this one
let mut plugins = self.plugins.lock().await;
plugins.retain(|p| p.dir != dir);
plugins.push(plugin_handle.clone());
let _ = match event.payload {
InternalEventPayload::BootResponse(resp) => resp,
_ => return Err(UnknownEventErr),
};
Ok(())
}
@@ -358,7 +373,7 @@ impl PluginManager {
payload: &InternalEventPayload,
plugins: Vec<PluginHandle>,
) -> Result<Vec<InternalEvent>> {
let label = format!("wait[{}]", plugins.len());
let label = format!("wait[{}.{}]", plugins.len(), payload.type_name());
let (rx_id, mut rx) = self.subscribe(label.as_str()).await;
// 1. Build the events with IDs and everything
@@ -406,7 +421,7 @@ impl PluginManager {
let events = sub_events_fut.await.expect("Thread didn't succeed");
// 5. Unsubscribe
self.unsubscribe(rx_id.as_str()).await;
self.unsubscribe(&rx_id).await;
Ok(events)
}
@@ -569,6 +584,7 @@ impl PluginManager {
pub async fn get_http_authentication_config<R: Runtime>(
&self,
window: &WebviewWindow<R>,
environment_chain: Vec<Environment>,
auth_name: &str,
values: HashMap<String, JsonPrimitive>,
request_id: &str,
@@ -579,13 +595,23 @@ impl PluginManager {
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
.ok_or(PluginNotFoundErr(auth_name.into()))?;
let vars = &make_vars_hashmap(environment_chain);
let cb = PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(&window),
RenderPurpose::Preview,
);
let rendered_values = render_json_value_raw(json!(values), vars, &cb).await?;
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
let event = self
.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
&plugin,
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
GetHttpAuthenticationConfigRequest { values, context_id },
GetHttpAuthenticationConfigRequest {
values: serde_json::from_value(rendered_values)?,
context_id,
},
),
)
.await?;
@@ -602,11 +628,23 @@ impl PluginManager {
pub async fn call_http_authentication_action<R: Runtime>(
&self,
window: &WebviewWindow<R>,
environment_chain: Vec<Environment>,
auth_name: &str,
action_index: i32,
values: HashMap<String, JsonPrimitive>,
model_id: &str,
) -> Result<()> {
let vars = &make_vars_hashmap(environment_chain);
let rendered_values = render_json_value_raw(
json!(values),
vars,
&PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(&window),
RenderPurpose::Preview,
),
)
.await?;
let results = self.get_http_authentication_summaries(window).await?;
let plugin = results
.iter()
@@ -621,7 +659,10 @@ impl PluginManager {
CallHttpAuthenticationActionRequest {
index: action_index,
plugin_ref_id: plugin.clone().ref_id,
args: CallHttpAuthenticationActionArgs { context_id, values },
args: CallHttpAuthenticationActionArgs {
context_id,
values: serde_json::from_value(rendered_values)?,
},
},
),
)

View File

@@ -1,7 +1,5 @@
use crate::error::Result;
use log::info;
use serde;
use serde::Deserialize;
use log::{info, warn};
use std::net::SocketAddr;
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager, Runtime};
@@ -9,12 +7,6 @@ use tauri_plugin_shell::process::CommandEvent;
use tauri_plugin_shell::ShellExt;
use tokio::sync::watch::Receiver;
#[derive(Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
struct PortFile {
port: i32,
}
pub async fn start_nodejs_plugin_runtime<R: Runtime>(
app: &AppHandle<R>,
addr: SocketAddr,
@@ -44,10 +36,10 @@ pub async fn start_nodejs_plugin_runtime<R: Runtime>(
while let Some(event) = child_rx.recv().await {
match event {
CommandEvent::Stderr(line) => {
print!("{}", String::from_utf8(line).unwrap());
warn!("{}", String::from_utf8_lossy(&line).trim_end_matches(&['\n', '\r'][..]));
}
CommandEvent::Stdout(line) => {
print!("{}", String::from_utf8(line).unwrap());
info!("{}", String::from_utf8_lossy(&line).trim_end_matches(&['\n', '\r'][..]));
}
_ => {}
}

View File

@@ -2,7 +2,6 @@ use crate::error::Result;
use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext};
use crate::plugin_meta::{PluginMetadata, get_plugin_meta};
use crate::util::gen_id;
use log::info;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
@@ -58,13 +57,6 @@ impl PluginHandle {
}
}
pub async fn terminate(&self, window_context: &PluginWindowContext) -> Result<()> {
info!("Terminating plugin {}", self.dir);
let event =
self.build_event_to_send(window_context, &InternalEventPayload::TerminateRequest, None);
self.send(&event).await
}
pub async fn send(&self, event: &InternalEvent) -> Result<()> {
self.to_plugin_tx.lock().await.send(event.to_owned()).await?;
Ok(())

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };

View File

@@ -109,24 +109,18 @@ pub(crate) async fn send<R: Runtime>(
window: WebviewWindow<R>,
ws_manager: State<'_, Mutex<WebsocketManager>>,
) -> Result<WebsocketConnection> {
let (connection, unrendered_request) = {
let db = app_handle.db();
let connection = db.get_websocket_connection(connection_id)?;
let unrendered_request = db.get_websocket_request(&connection.request_id)?;
(connection, unrendered_request)
};
let environment = match environment_id {
Some(id) => Some(app_handle.db().get_environment(id)?),
None => None,
};
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let connection = app_handle.db().get_websocket_connection(connection_id)?;
let unrendered_request = app_handle.db().get_websocket_request(&connection.request_id)?;
let environment_chain = app_handle.db().resolve_environments(
&unrendered_request.workspace_id,
unrendered_request.folder_id.as_deref(),
environment_id,
)?;
let (resolved_request, _auth_context_id) =
resolve_websocket_request(&window, &unrendered_request)?;
let request = render_websocket_request(
&resolved_request,
&base_environment,
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -192,19 +186,17 @@ pub(crate) async fn connect<R: Runtime>(
ws_manager: State<'_, Mutex<WebsocketManager>>,
) -> Result<WebsocketConnection> {
let unrendered_request = app_handle.db().get_websocket_request(request_id)?;
let environment = match environment_id {
Some(id) => Some(app_handle.db().get_environment(id)?),
None => None,
};
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let environment_chain = app_handle.db().resolve_environments(
&unrendered_request.workspace_id,
unrendered_request.folder_id.as_deref(),
environment_id,
)?;
let workspace = app_handle.db().get_workspace(&unrendered_request.workspace_id)?;
let (resolved_request, auth_context_id) =
resolve_websocket_request(&window, &unrendered_request)?;
let request = render_websocket_request(
&resolved_request,
&base_environment,
environment.as_ref(),
environment_chain,
&PluginTemplateCallback::new(
&app_handle,
&PluginWindowContext::new(&window),
@@ -305,7 +297,7 @@ pub(crate) async fn connect<R: Runtime>(
// Add cookies to WS HTTP Upgrade
if let Some(id) = cookie_jar_id {
let cookie_jar = app_handle.db().get_cookie_jar(id)?;
let cookie_jar = app_handle.db().get_cookie_jar(&id)?;
let cookies = cookie_jar
.cookies

View File

@@ -7,16 +7,19 @@ use tokio_tungstenite::tungstenite::handshake::client::Response;
use tokio_tungstenite::tungstenite::http::HeaderValue;
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
use tokio_tungstenite::{
connect_async_tls_with_config, Connector, MaybeTlsStream, WebSocketStream,
Connector, MaybeTlsStream, WebSocketStream, connect_async_tls_with_config,
};
// Enabling ALPN breaks websocket requests
const WITH_ALPN: bool = false;
pub(crate) async fn ws_connect(
url: &str,
headers: HeaderMap<HeaderValue>,
validate_certificates: bool,
) -> crate::error::Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response)> {
info!("Connecting to WS {url}");
let tls_config = yaak_http::tls::get_config(validate_certificates);
let tls_config = yaak_http::tls::get_config(validate_certificates, WITH_ALPN);
let mut req = url.into_client_request()?;
let req_headers = req.headers_mut();
@@ -34,4 +37,4 @@ pub(crate) async fn ws_connect(
)
.await?;
Ok((stream, response))
}
}

View File

@@ -6,11 +6,10 @@ use yaak_templates::{parse_and_render, render_json_value_raw, TemplateCallback};
pub async fn render_websocket_request<T: TemplateCallback>(
r: &WebsocketRequest,
base_environment: &Environment,
environment: Option<&Environment>,
environment_chain: Vec<Environment>,
cb: &T,
) -> Result<WebsocketRequest> {
let vars = &make_vars_hashmap(base_environment, environment);
let vars = &make_vars_hashmap(environment_chain);
let mut headers = Vec::new();
for p in r.headers.clone() {

View File

@@ -37,7 +37,8 @@ export const createEnvironmentAndActivate = createFastMutation<
name,
variables: [],
workspaceId,
base: false,
parentId: baseEnvironment.id,
parentModel: 'environment',
});
},
onSuccess: async (environmentId) => {

View File

@@ -27,7 +27,8 @@ import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { showDialog, toggleDialog } from '../lib/dialog';
import { showDialog } from '../lib/dialog';
import { editEnvironment } from '../lib/editEnvironment';
import { renameModelWithPrompt } from '../lib/renameModelWithPrompt';
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
import { router } from '../lib/router';
@@ -40,7 +41,6 @@ import { HttpMethodTag } from './core/HttpMethodTag';
import { Icon } from './core/Icon';
import { PlainInput } from './core/PlainInput';
import { HStack } from './core/Stacks';
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
interface CommandPaletteGroup {
key: string;
@@ -125,15 +125,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
key: 'environment.edit',
label: 'Edit Environment',
action: 'environmentEditor.toggle',
onSelect: () => {
toggleDialog({
id: 'environment-editor',
noPadding: true,
size: 'lg',
className: 'h-[80vh]',
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
});
},
onSelect: () => editEnvironment(activeEnvironment),
},
{
key: 'environment.create',

View File

@@ -51,7 +51,7 @@ export function ConfirmLargeResponse({ children, response }: Props) {
color="secondary"
variant="border"
size="xs"
text={() => getResponseBodyText(response)}
text={() => getResponseBodyText({ responseId: response.id, filter: null })}
/>
)}
</HStack>

View File

@@ -182,23 +182,24 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
);
case 'accordion':
return (
<DetailsBanner
key={i}
summary={input.label}
className={classNames(disabled && 'opacity-disabled')}
>
<div className="mb-3 px-3">
<FormInputs
data={data}
disabled={disabled}
inputs={input.inputs}
setDataAttr={setDataAttr}
stateKey={stateKey}
autocompleteFunctions={autocompleteFunctions || false}
autocompleteVariables={autocompleteVariables}
/>
</div>
</DetailsBanner>
<div key={i}>
<DetailsBanner
summary={input.label}
className={classNames('!mb-auto', disabled && 'opacity-disabled')}
>
<div className="mb-3 px-3">
<FormInputs
data={data}
disabled={disabled}
inputs={input.inputs}
setDataAttr={setDataAttr}
stateKey={stateKey}
autocompleteFunctions={autocompleteFunctions || false}
autocompleteVariables={autocompleteVariables}
/>
</div>
</DetailsBanner>
</div>
);
case 'banner':
return (
@@ -309,6 +310,7 @@ function EditorArg({
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
disabled={arg.disabled}
language={arg.language}
readOnly={arg.readOnly}
onChange={onChange}
heightMode="auto"
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
@@ -329,9 +331,9 @@ function EditorArg({
showDialog({
id: 'id',
size: 'full',
title: 'Edit Value',
title: arg.readOnly ? 'View Value' : 'Edit Value',
className: '!max-w-[50rem] !max-h-[60rem]',
description: (
description: arg.label && (
<Label
htmlFor={id}
required={!arg.optional}
@@ -355,6 +357,7 @@ function EditorArg({
}
disabled={arg.disabled}
language={arg.language}
readOnly={arg.readOnly}
onChange={onChange}
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
placeholder={arg.placeholder ?? undefined}

View File

@@ -1,16 +1,15 @@
import classNames from 'classnames';
import { memo, useCallback, useMemo } from 'react';
import { memo, useMemo } from 'react';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { toggleDialog } from '../lib/dialog';
import { editEnvironment } from '../lib/editEnvironment';
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
import { Button } from './core/Button';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
type Props = {
className?: string;
@@ -23,16 +22,6 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
const { subEnvironments, baseEnvironment } = useEnvironmentsBreakdown();
const activeEnvironment = useActiveEnvironment();
const showEnvironmentDialog = useCallback(() => {
toggleDialog({
id: 'environment-editor',
noPadding: true,
size: 'lg',
className: 'h-[80vh]',
render: () => <EnvironmentEditDialog initialEnvironment={activeEnvironment} />,
});
}, [activeEnvironment]);
const items: DropdownItem[] = useMemo(
() => [
...subEnvironments.map(
@@ -55,14 +44,13 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
? [{ type: 'separator', label: 'Environments' }]
: []) as DropdownItem[]),
{
key: 'edit',
label: 'Manage Environments',
hotKeyAction: 'environmentEditor.toggle',
leftSlot: <Icon icon="box" />,
onSelect: showEnvironmentDialog,
onSelect: () => editEnvironment(activeEnvironment),
},
],
[activeEnvironment?.id, subEnvironments, showEnvironmentDialog],
[subEnvironments, activeEnvironment],
);
const hasBaseVars =
@@ -79,7 +67,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
)}
// If no environments, the button simply opens the dialog.
// NOTE: We don't create a new button because we want to reuse the hotkey from the menu items
onClick={subEnvironments.length === 0 ? showEnvironmentDialog : undefined}
onClick={subEnvironments.length === 0 ? () => editEnvironment(null) : undefined}
{...buttonProps}
>
<EnvironmentColorIndicator environment={activeEnvironment ?? null} />

View File

@@ -1,42 +1,28 @@
import type { Environment } from '@yaakapp-internal/models';
import { duplicateModel, patchModel } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useState } from 'react';
import { createEnvironmentAndActivate } from '../commands/createEnvironment';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useIsEncryptionEnabled } from '../hooks/useIsEncryptionEnabled';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRandomKey } from '../hooks/useRandomKey';
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
import { analyzeTemplate, convertTemplateToSecure } from '../lib/encryption';
import { isBaseEnvironment } from '../lib/model_util';
import { showPrompt } from '../lib/prompt';
import { resolvedModelName } from '../lib/resolvedModelName';
import {
setupOrConfigureEncryption,
withEncryptionEnabled,
} from '../lib/setupOrConfigureEncryption';
import { showColorPicker } from '../lib/showColorPicker';
import { BadgeButton } from './core/BadgeButton';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { DismissibleBanner } from './core/DismissibleBanner';
import type { DropdownItem } from './core/Dropdown';
import { ContextMenu } from './core/Dropdown';
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
import { Heading } from './core/Heading';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { IconTooltip } from './core/IconTooltip';
import { InlineCode } from './core/InlineCode';
import type { PairWithId } from './core/PairEditor';
import { ensurePairId } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { Separator } from './core/Separator';
import { SplitLayout } from './core/SplitLayout';
import { VStack } from './core/Stacks';
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
import { EnvironmentEditor } from './EnvironmentEditor';
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
interface Props {
initialEnvironment: Environment | null;
@@ -97,13 +83,13 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
<aside className="w-full min-w-0 pt-2">
<div className="min-w-0 h-full overflow-y-auto pt-1">
{[baseEnvironment, ...otherBaseEnvironments].map((e) => (
<SidebarButton
<EnvironmentDialogSidebarButton
key={e.id}
active={selectedEnvironment?.id == e.id}
onClick={() => setSelectedEnvironmentId(e.id)}
environment={e}
duplicateEnvironment={handleDuplicateEnvironment}
// Allow deleting base environment if there are multiples
// Allow deleting the base environment if there are multiples
deleteEnvironment={
otherBaseEnvironments.length > 0 ? handleDeleteEnvironment : null
}
@@ -121,7 +107,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
}
>
{resolvedModelName(e)}
</SidebarButton>
</EnvironmentDialogSidebarButton>
))}
{subEnvironments.length > 0 && (
<div className="px-2">
@@ -129,7 +115,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
</div>
)}
{subEnvironments.map((e) => (
<SidebarButton
<EnvironmentDialogSidebarButton
key={e.id}
active={selectedEnvironment?.id === e.id}
environment={e}
@@ -139,7 +125,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
deleteEnvironment={handleDeleteEnvironment}
>
{e.name}
</SidebarButton>
</EnvironmentDialogSidebarButton>
))}
</div>
</aside>
@@ -153,7 +139,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
</div>
) : (
<EnvironmentEditor
className="pt-2 border-l border-border-subtle"
className="pl-4 pt-3 border-l border-border-subtle"
environment={selectedEnvironment}
/>
)
@@ -162,139 +148,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
);
};
const EnvironmentEditor = function ({
environment: selectedEnvironment,
className,
}: {
environment: Environment;
className?: string;
}) {
const workspaceId = selectedEnvironment.workspaceId;
const isEncryptionEnabled = useIsEncryptionEnabled();
const valueVisibility = useKeyValue<boolean>({
namespace: 'global',
key: ['environmentValueVisibility', workspaceId],
fallback: false,
});
const { allEnvironments } = useEnvironmentsBreakdown();
const handleChange = useCallback(
(variables: PairWithId[]) => patchModel(selectedEnvironment, { variables }),
[selectedEnvironment],
);
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
// Gather a list of env names from other environments to help the user get them aligned
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
const options: GenericCompletionOption[] = [];
if (selectedEnvironment.base) {
return { options };
}
const allVariables = allEnvironments.flatMap((e) => e?.variables);
const allVariableNames = new Set(allVariables.map((v) => v?.name));
for (const name of allVariableNames) {
const containingEnvs = allEnvironments.filter((e) =>
e.variables.some((v) => v.name === name),
);
const isAlreadyInActive = containingEnvs.find((e) => e.id === selectedEnvironment.id);
if (isAlreadyInActive) continue;
options.push({
label: name,
type: 'constant',
detail: containingEnvs.map((e) => e.name).join(', '),
});
}
return { options };
}, [selectedEnvironment.base, selectedEnvironment.id, allEnvironments]);
const validateName = useCallback((name: string) => {
// Empty just means the variable doesn't have a name yet and is unusable
if (name === '') return true;
return name.match(/^[a-z_][a-z0-9_-]*$/i) != null;
}, []);
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
const promptToEncrypt = useMemo(() => {
if (!isEncryptionEnabled) {
return true;
} else {
return !selectedEnvironment.variables.every(
(v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure',
);
}
}, [selectedEnvironment.variables, isEncryptionEnabled]);
const encryptEnvironment = (environment: Environment) => {
withEncryptionEnabled(async () => {
const encryptedVariables: PairWithId[] = [];
for (const variable of environment.variables) {
const value = variable.value ? await convertTemplateToSecure(variable.value) : '';
encryptedVariables.push(ensurePairId({ ...variable, value }));
}
await handleChange(encryptedVariables);
regenerateForceUpdateKey();
});
};
return (
<VStack space={4} className={classNames(className, 'pl-4')}>
<Heading className="w-full flex items-center gap-0.5">
<EnvironmentColorIndicator clickToEdit environment={selectedEnvironment ?? null} />
<div className="mr-2">{selectedEnvironment?.name}</div>
{isEncryptionEnabled ? (
promptToEncrypt ? (
<BadgeButton color="notice" onClick={() => encryptEnvironment(selectedEnvironment)}>
Encrypt All Variables
</BadgeButton>
) : (
<BadgeButton color="secondary" onClick={setupOrConfigureEncryption}>
Encryption Settings
</BadgeButton>
)
) : (
<>
<BadgeButton color="secondary" onClick={() => valueVisibility.set((v) => !v)}>
{valueVisibility.value ? 'Conceal Values' : 'Reveal Values'}
</BadgeButton>
</>
)}
</Heading>
{selectedEnvironment.public && promptToEncrypt && (
<DismissibleBanner
id={`warn-unencrypted-${selectedEnvironment.id}`}
color="notice"
className="mr-3"
>
This environment is sharable. Ensure variable values are encrypted to avoid accidental
leaking of secrets during directory sync or data export.
</DismissibleBanner>
)}
<div className="h-full pr-2 pb-2 grid grid-rows-[minmax(0,1fr)] overflow-auto">
<PairOrBulkEditor
allowMultilineValues
preferenceName="environment"
nameAutocomplete={nameAutocomplete}
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueType}
valueAutocompleteVariables
valueAutocompleteFunctions
forceUpdateKey={`${selectedEnvironment.id}::${forceUpdateKey}`}
pairs={selectedEnvironment.variables}
onChange={handleChange}
stateKey={`environment.${selectedEnvironment.id}`}
forcedEnvironmentId={
// Editing the base environment should resolve variables using the active environment.
// Editing a sub environment should resolve variables as if it's the active environment
selectedEnvironment.base ? undefined : selectedEnvironment.id
}
/>
</div>
</VStack>
);
};
function SidebarButton({
function EnvironmentDialogSidebarButton({
children,
className,
active,
@@ -359,7 +213,7 @@ function SidebarButton({
{
label: 'Rename',
leftSlot: <Icon icon="pencil" />,
hidden: environment.base,
hidden: isBaseEnvironment(environment),
onSelect: async () => {
const name = await showPrompt({
id: 'rename-environment',
@@ -392,23 +246,13 @@ function SidebarButton({
{
label: environment.color ? 'Change Color' : 'Assign Color',
leftSlot: <Icon icon="palette" />,
hidden: environment.base,
hidden: isBaseEnvironment(environment),
onSelect: async () => showColorPicker(environment),
},
{
label: `Make ${environment.public ? 'Private' : 'Sharable'}`,
leftSlot: <Icon icon={environment.public ? 'eye_closed' : 'eye'} />,
rightSlot: (
<IconTooltip
content={
<>
Sharable environments will be included in Directory Sync or data export. It is
recommended to encrypt all variable values within sharable environments to
prevent accidentally leaking secrets.
</>
}
/>
),
rightSlot: <EnvironmentSharableTooltip />,
onSelect: async () => {
await patchModel(environment, { public: !environment.public });
},

View File

@@ -0,0 +1,169 @@
import type { Environment } from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import React, { useCallback, useMemo } from 'react';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useIsEncryptionEnabled } from '../hooks/useIsEncryptionEnabled';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRandomKey } from '../hooks/useRandomKey';
import { analyzeTemplate, convertTemplateToSecure } from '../lib/encryption';
import { isBaseEnvironment } from '../lib/model_util';
import {
setupOrConfigureEncryption,
withEncryptionEnabled,
} from '../lib/setupOrConfigureEncryption';
import { BadgeButton } from './core/BadgeButton';
import { DismissibleBanner } from './core/DismissibleBanner';
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
import { Heading } from './core/Heading';
import type { PairWithId } from './core/PairEditor';
import { ensurePairId } from './core/PairEditor';
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
import { VStack } from './core/Stacks';
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
export function EnvironmentEditor({
environment: selectedEnvironment,
hideName,
className,
}: {
environment: Environment;
hideName?: boolean;
className?: string;
}) {
const workspaceId = selectedEnvironment.workspaceId;
const isEncryptionEnabled = useIsEncryptionEnabled();
const valueVisibility = useKeyValue<boolean>({
namespace: 'global',
key: ['environmentValueVisibility', workspaceId],
fallback: false,
});
const { allEnvironments } = useEnvironmentsBreakdown();
const handleChange = useCallback(
(variables: PairWithId[]) => patchModel(selectedEnvironment, { variables }),
[selectedEnvironment],
);
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
// Gather a list of env names from other environments to help the user get them aligned
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
const options: GenericCompletionOption[] = [];
if (isBaseEnvironment(selectedEnvironment)) {
return { options };
}
const allVariables = allEnvironments.flatMap((e) => e?.variables);
const allVariableNames = new Set(allVariables.map((v) => v?.name));
for (const name of allVariableNames) {
const containingEnvs = allEnvironments.filter((e) =>
e.variables.some((v) => v.name === name),
);
const isAlreadyInActive = containingEnvs.find((e) => e.id === selectedEnvironment.id);
if (isAlreadyInActive) continue;
options.push({
label: name,
type: 'constant',
detail: containingEnvs.map((e) => e.name).join(', '),
});
}
return { options };
}, [selectedEnvironment, allEnvironments]);
const validateName = useCallback((name: string) => {
// Empty just means the variable doesn't have a name yet and is unusable
if (name === '') return true;
return name.match(/^[a-z_][a-z0-9_-]*$/i) != null;
}, []);
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
const allVariableAreEncrypted = useMemo(
() =>
selectedEnvironment.variables.every(
(v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure',
),
[selectedEnvironment.variables],
);
const encryptEnvironment = (environment: Environment) => {
withEncryptionEnabled(async () => {
const encryptedVariables: PairWithId[] = [];
for (const variable of environment.variables) {
const value = variable.value ? await convertTemplateToSecure(variable.value) : '';
encryptedVariables.push(ensurePairId({ ...variable, value }));
}
await handleChange(encryptedVariables);
regenerateForceUpdateKey();
});
};
return (
<VStack space={4} className={className}>
<Heading className="w-full flex items-center gap-0.5">
<EnvironmentColorIndicator clickToEdit environment={selectedEnvironment ?? null} />
{!hideName && <div className="mr-2">{selectedEnvironment?.name}</div>}
{isEncryptionEnabled ? (
!allVariableAreEncrypted ? (
<BadgeButton color="notice" onClick={() => encryptEnvironment(selectedEnvironment)}>
Encrypt All Variables
</BadgeButton>
) : (
<BadgeButton color="secondary" onClick={setupOrConfigureEncryption}>
Encryption Settings
</BadgeButton>
)
) : (
<BadgeButton color="secondary" onClick={() => valueVisibility.set((v) => !v)}>
{valueVisibility.value ? 'Hide Values' : 'Show Values'}
</BadgeButton>
)}
<BadgeButton
color="secondary"
rightSlot={<EnvironmentSharableTooltip />}
onClick={async () => {
await patchModel(selectedEnvironment, { public: !selectedEnvironment.public });
}}
>
{selectedEnvironment.public ? 'Sharable' : 'Private'}
</BadgeButton>
</Heading>
{selectedEnvironment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
<DismissibleBanner
id={`warn-unencrypted-${selectedEnvironment.id}`}
color="notice"
className="mr-3"
actions={[
{
label: 'Encrypt Variables',
onClick: () => encryptEnvironment(selectedEnvironment),
color: 'primary',
},
]}
>
This sharable environment contains plain-text secrets
</DismissibleBanner>
)}
<div className="h-full pr-2 pb-2 grid grid-rows-[minmax(0,1fr)] overflow-auto">
<PairOrBulkEditor
allowMultilineValues
preferenceName="environment"
nameAutocomplete={nameAutocomplete}
namePlaceholder="VAR_NAME"
nameValidate={validateName}
valueType={valueType}
valueAutocompleteVariables
valueAutocompleteFunctions
forceUpdateKey={`${selectedEnvironment.id}::${forceUpdateKey}`}
pairs={selectedEnvironment.variables}
onChange={handleChange}
stateKey={`environment.${selectedEnvironment.id}`}
forcedEnvironmentId={
// Editing the base environment should resolve variables using the active environment.
// Editing a sub environment should resolve variables as if it's the active environment
isBaseEnvironment(selectedEnvironment) ? undefined : selectedEnvironment.id
}
/>
</div>
</VStack>
);
}

View File

@@ -0,0 +1,8 @@
import React from 'react';
import { IconTooltip } from './core/IconTooltip';
export function EnvironmentSharableTooltip() {
return (
<IconTooltip content="Sharable environments are included in Directory Sync and data export." />
);
}

View File

@@ -1,13 +1,19 @@
import { foldersAtom, patchModel } from '@yaakapp-internal/models';
import { createWorkspaceModel, foldersAtom, patchModel } from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai';
import { useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { useAuthTab } from '../hooks/useAuthTab';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useHeadersTab } from '../hooks/useHeadersTab';
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
import { Button } from './core/Button';
import { CountBadge } from './core/CountBadge';
import { Input } from './core/Input';
import { Link } from './core/Link';
import { VStack } from './core/Stacks';
import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { EnvironmentEditor } from './EnvironmentEditor';
import { HeadersEditor } from './HeadersEditor';
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
import { MarkdownEditor } from './MarkdownEditor';
@@ -19,9 +25,10 @@ interface Props {
const TAB_AUTH = 'auth';
const TAB_HEADERS = 'headers';
const TAB_VARIABLES = 'variables';
const TAB_GENERAL = 'general';
export type FolderSettingsTab = typeof TAB_AUTH | typeof TAB_HEADERS | typeof TAB_GENERAL;
export type FolderSettingsTab = typeof TAB_AUTH | typeof TAB_HEADERS | typeof TAB_GENERAL | typeof TAB_VARIABLES;
export function FolderSettingsDialog({ folderId, tab }: Props) {
const folders = useAtomValue(foldersAtom);
@@ -30,6 +37,11 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
const authTab = useAuthTab(TAB_AUTH, folder);
const headersTab = useHeadersTab(TAB_HEADERS, folder);
const inheritedHeaders = useInheritedHeaders(folder);
const environments = useEnvironmentsBreakdown();
const folderEnvironment = environments.allEnvironments.find(
(e) => e.parentModel === 'folder' && e.parentId === folderId,
);
const numVars = (folderEnvironment?.variables ?? []).filter((v) => v.name).length;
const tabs = useMemo<TabItem[]>(() => {
if (folder == null) return [];
@@ -39,10 +51,15 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
value: TAB_GENERAL,
label: 'General',
},
...authTab,
...headersTab,
...authTab,
{
value: TAB_VARIABLES,
label: 'Variables',
rightSlot: numVars > 0 ? <CountBadge count={numVars} /> : null,
},
];
}, [authTab, folder, headersTab]);
}, [authTab, folder, headersTab, numVars]);
if (folder == null) return null;
@@ -85,6 +102,38 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
stateKey={`headers.${folder.id}`}
/>
</TabContent>
<TabContent value={TAB_VARIABLES} className="pt-3 overflow-y-auto h-full px-4">
{folderEnvironment == null ? (
<EmptyStateText>
<VStack alignItems="center" space={1.5}>
<p>
Override{' '}
<Link href="https://feedback.yaak.app/help/articles/3284139-environments-and-variables">
Variables
</Link>{' '}
for requests within this folder.
</p>
<Button
variant="border"
size="sm"
onClick={async () => {
await createWorkspaceModel({
workspaceId: folder.workspaceId,
parentModel: 'folder',
parentId: folder.id,
model: 'environment',
name: 'Folder Environment',
});
}}
>
Create Folder Environment
</Button>
</VStack>
</EmptyStateText>
) : (
<EnvironmentEditor hideName environment={folderEnvironment} />
)}
</TabContent>
</Tabs>
);
}

View File

@@ -43,7 +43,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
return (
<VStack className="flex-col-reverse mb-3" space={3}>
{/* Buttons on top so they get focus first */}
<HStack space={2} justifyContent="start" className="flex-row-reverse">
<HStack space={2} justifyContent="start" className="flex-row-reverse mt-3">
<Button
color="primary"
variant="border"
@@ -135,9 +135,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
<table className="w-full divide-y divide-surface-highlight">
<thead>
<tr>
<th />
<th className="text-text-subtlest">Added File Paths</th>
<th />
<th className="text-text-subtlest" colSpan={3}>Added File Paths</th>
</tr>
</thead>
<tbody className="divide-y divide-surface-highlight">

View File

@@ -1,9 +1,10 @@
import { type } from '@tauri-apps/plugin-os';
import { settingsAtom } from '@yaakapp-internal/models';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
import React, { useMemo } from 'react';
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
import { useIsFullscreen } from '../hooks/useIsFullscreen';
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
import { WindowControls } from './WindowControls';
@@ -23,7 +24,7 @@ export function HeaderSize({
children,
}: HeaderSizeProps) {
const settings = useAtomValue(settingsAtom);
const stoplightsVisible = useStoplightsVisible();
const isFullscreen = useIsFullscreen();
const finalStyle = useMemo<CSSProperties>(() => {
const s = { ...style };
@@ -31,20 +32,22 @@ export function HeaderSize({
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
// Add large padding for window controls
if (stoplightsVisible && !ignoreControlsSpacing) {
s.paddingLeft = 72 / settings.interfaceScale;
} else if (!stoplightsVisible && !ignoreControlsSpacing && !settings.hideWindowControls) {
if (type() === 'macos') {
if (!isFullscreen) {
// Add large padding for window controls
s.paddingLeft = 72 / settings.interfaceScale;
}
} else if (!ignoreControlsSpacing && !settings.hideWindowControls) {
s.paddingRight = WINDOW_CONTROLS_WIDTH;
}
return s;
}, [
ignoreControlsSpacing,
isFullscreen,
settings.hideWindowControls,
settings.interfaceScale,
size,
stoplightsVisible,
style,
]);

Some files were not shown because too many files have changed in this diff Show More