Compare commits

...

738 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 0cb42dedc9 Initial plan 2025-12-22 18:54:12 +00:00
Gregory Schier 089c7e8dce Http response events (#326) 2025-12-21 14:34:37 -08:00
Gregory Schier 7e0aa919fb Immediate cancellation 2025-12-21 06:28:36 -08:00
Gregory Schier 5776bab288 Tweak response pane and refactor timings 2025-12-21 06:24:01 -08:00
Gregory Schier 6b52a0cbed Try fix tests on Windows 2025-12-20 14:48:23 -08:00
Gregory Schier 46933059f6 Split up HTTP sending logic (#320) 2025-12-20 14:10:55 -08:00
Gregory Schier cfbfd66eef Reformat project 2025-12-13 08:10:12 -08:00
Gregory Schier c20c0eff32 Update entitlements.plist for 1Password shared lib 2025-12-11 09:22:27 -08:00
Gregory Schier 9d40949043 Fix warning: unused variable: window on non-mac OSs 2025-12-11 07:18:31 -08:00
Gregory Schier d435337f2a Don't strip symbols hotfix 2025-12-11 06:49:06 -08:00
Gregory Schier a32145c054 Merge branch 'hotfix/2025.9.3' 2025-12-11 06:32:35 -08:00
Gregory Schier e0f547b93f Update tauri 2025-12-11 06:32:14 -08:00
Gregory Schier 5d4268d6a1 Merge branch 'hotfix/2025.9.3' 2025-12-11 06:00:47 -08:00
Gregory Schier 0a3506f81e Also move defaultValue out 2025-12-11 05:59:40 -08:00
Gregory Schier 375b2287b7 Merge branch 'hotfix/2025.9.3' 2025-12-11 05:54:23 -08:00
Gregory Schier e72c1e68e5 Unify 1Password field back to static name 2025-12-11 05:48:19 -08:00
Gregory Schier 3484db3371 Default cert to open when just added 2025-12-10 15:08:59 -08:00
Gregory Schier c4b559f34b Support client certificates (#319) 2025-12-10 13:54:22 -08:00
Mikhail Mamontov ef1ba9b834 fix(gRPC): Cache descriptor pools to avoid re-reflection; add manual “Refresh Schema” to force re-fetch (#317) 2025-12-09 15:35:35 -08:00
Jake Oliver 846f4d9551 Update 1Password template to support the new Desktop authentication method (#316) 2025-12-09 14:50:08 -08:00
Gregory Schier 4780bfe41f Fix curl import: decode Unicode escape sequences in $'...' strings (#318)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 14:15:39 -08:00
Gregory Schier d0d01b3897 Update license check to use status instead of type 2025-12-09 14:12:13 -08:00
Gregory Schier fc1e8baa23 Catch any 4XX error on refresh token failure
https://feedback.yaak.app/p/folders-oauth2-refresh-token-issue
2025-12-09 14:08:31 -08:00
Gregory Schier d35116c494 Add license handling for expired licenses 2025-12-09 13:51:02 -08:00
gschier 1d257b365b Deploying to main from @ mountain-loop/yaak@1076d57e8a 🚀 2025-12-09 18:15:05 +00:00
Gregory Schier 1076d57e8a Remove unused funding model entries from FUNDING.yml 2025-12-09 10:14:19 -08:00
Gregory Schier 1c93d5775f Shorter titles when using native titlebar 2025-12-06 06:47:34 -08:00
Gregory Schier 7b78fac24e Fix native titlebar. Get menu ready for native Mac menus 2025-12-05 15:14:13 -08:00
Gregory Schier 6534b3f622 Debug Window 2025-12-05 17:37:54 -08:00
Gregory Schier daba21fbca Couple small fixes for Windows 2025-12-05 10:18:21 -08:00
Gregory Schier 3b99ea1cad Try fixing titlebar thing for Windows 2025-12-05 10:03:54 -08:00
Gregory Schier 937d7aa72a Fix Git invokation on Windows (#313) 2025-12-05 09:22:34 -08:00
Gregory Schier 5bf7278479 Add setting to use native window titlebar (#312) 2025-12-05 09:15:48 -08:00
Gregory Schier 095af8cf4b Refresh query when plugins reload in useTemplateFunctionConfig hook 2025-12-02 08:07:53 -08:00
Gregory Schier e1c1ecc34d Try fix quotes for Windows 2025-12-02 05:45:01 -08:00
Gregory Schier 6e4c167bfd Add beta warning banners to OAuth 1.0 and NTLM plugins 2025-12-01 09:26:55 -08:00
Gregory Schier 25d8357471 Merge remote-tracking branch 'origin/main' 2025-12-01 07:56:12 -08:00
Gregory Schier 8e00693af3 Fix JSON lint error location 2025-12-01 07:54:02 -08:00
gschier 079da67889 Deploying to main from @ mountain-loop/yaak@9ed3dacd28 🚀 2025-12-01 15:39:07 +00:00
Gregory Schier 9ed3dacd28 Fix dropdown menu keys
https://feedback.yaak.app/p/response-history-bug
2025-12-01 06:32:04 -08:00
Gregory Schier ba6e64ef37 Explicit targets 2025-11-28 09:07:01 -08:00
Gregory Schier d7a68c2d53 Fix Rust version 2025-11-28 09:04:59 -08:00
Gregory Schier e8e1d9246e Try using window-latest for ARM build 2025-11-28 08:53:44 -08:00
Gregory Schier a7574f2e5a Fix vendor scripts for arm 2025-11-28 08:16:24 -08:00
Gregory Schier 69f9661813 Upgrade @yaakapp/cli again 2025-11-28 07:49:54 -08:00
Gregory Schier 302b0a4747 Upgrade @yaakapp/cli 2025-11-28 07:10:27 -08:00
Gregory Schier 07f4696a2c Install wasm-pack from cargo for Windows ARM compat 2025-11-28 06:04:48 -08:00
Gregory Schier 2ddb1096df Try building Windows ARM too 2025-11-27 15:41:17 -08:00
Gregory Schier 0149355d66 Try fix xdg-mime missing on ARM 2025-11-27 14:10:13 -08:00
Gregory Schier 2e7749a883 Fix? 2025-11-27 13:53:50 -08:00
Gregory Schier cd0e8c0bc2 Try arm linux builds 2025-11-27 13:47:58 -08:00
Gregory Schier 64e4e352a0 Fix package order 2025-11-27 13:46:30 -08:00
Gregory Schier b512365f5a Oops, remove body too 2025-11-27 13:31:16 -08:00
Gregory Schier 13c84e3fb6 Add doNotEncodePath to aws4 plugin 2025-11-27 13:29:59 -08:00
Gregory Schier 8d1b17cac1 Add previewArgs support for template functions and enhance validation logic for form inputs 2025-11-27 12:55:39 -08:00
Gregory Schier 0c7034eefc Fix text cutting off on <Select/> 2025-11-27 06:22:29 -08:00
Gregory Schier 3ec236462f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src-web/components/MarkdownEditor.tsx
2025-11-26 14:12:46 -08:00
Gregory Schier 1b5ac6fc89 Some fixes 2025-11-26 14:10:02 -08:00
Gregory Schier d356bac135 Fix icon and segmented control and dev icon 2025-11-26 11:22:10 -08:00
Gregory Schier 8a80e7b833 Add ability to conditionally disable auth (#309) 2025-11-26 11:05:07 -08:00
Gregory Schier a1ae065d37 PR feedback 2025-11-26 11:01:13 -08:00
Gregory Schier 79dd50474d Conditionally disable auth 2025-11-26 10:30:16 -08:00
Gregory Schier dfa6f1c5b4 Fix collapse all folders 2025-11-26 07:19:12 -08:00
Gregory Schier 2edd33b6e3 TSC check and set editor key 2025-11-26 06:25:02 -08:00
Gregory Schier 8b851d4685 Fix better 2025-11-25 09:46:02 -08:00
Gregory Schier 20e1b5c00e Fix dialog and invalid variable style 2025-11-25 09:37:19 -08:00
Gregory Schier c4ab2965f7 Scrollable tables, specify multi-part filename, fix required prop in prompt, better tab padding 2025-11-25 08:45:33 -08:00
Gregory Schier 0cad8f69e2 Fix imports 2025-11-24 08:55:55 -08:00
Zhizhen He a8402824ed Fix useState (#307) 2025-11-24 08:55:37 -08:00
Gregory Schier acf9458616 Enable updater artifacts creation in Tauri release configuration 2025-11-23 09:26:34 -08:00
Gregory Schier 0a58f7dfc8 Add back vendor-node 2025-11-23 08:58:57 -08:00
Gregory Schier 6e05d85ae4 Move icon definition to make Windows tests pass 2025-11-23 08:55:47 -08:00
Gregory Schier a04db485de Run CI on main 2025-11-23 08:46:26 -08:00
Gregory Schier d7043e75d6 Biome tweaks 2025-11-23 08:45:15 -08:00
Gregory Schier ec3e2e16a9 Switch to BiomeJS (#306) 2025-11-23 08:38:13 -08:00
Gregory Schier 2bac610efe Official 1Password Template Function (#305) 2025-11-22 06:08:13 -08:00
Gregory Schier 43a7132014 Support Any type for gRPC reflection 2025-11-21 13:15:09 -08:00
Gregory Schier bddc6e35a0 Bail out if sync directory is deleted 2025-11-19 13:42:48 -08:00
Gregory Schier 0e98a3e498 Fix prompt default value 2025-11-19 10:10:13 -08:00
Gregory Schier 17b6c945e6 Cap max height on template function dialog 2025-11-19 09:39:43 -08:00
Gregory Schier 474e761eb7 Fix prompt 2025-11-19 09:21:59 -08:00
Gregory Schier 1fbf9e50c4 Better keychain function descriptions 2025-11-19 09:15:50 -08:00
Gregory Schier 6863decd8e Fix sidebar scroll into view 2025-11-18 13:30:37 -08:00
Gregory Schier 569e506f32 Try rel imports 2025-11-18 09:09:56 -08:00
Gregory Schier 6d7a08758f Try rel imports 2025-11-18 09:09:39 -08:00
Gregory Schier 20dfd50a7d Try without && 2025-11-18 09:08:16 -08:00
Gregory Schier d747eb5e45 Try again 2025-11-18 09:02:10 -08:00
Gregory Schier 81fca7c54f Reverse order 2025-11-18 08:55:46 -08:00
Gregory Schier 5465efea84 Don't build in vendor-plugins 2025-11-18 08:53:55 -08:00
Gregory Schier 96a3630725 Fix package build order 2025-11-18 08:48:58 -08:00
Gregory Schier f1b6c89186 Fix package types? 2025-11-18 08:39:07 -08:00
Gregory Schier 9c52652a5e Move a bunch of git ops to use the git binary (#302) 2025-11-17 15:22:39 -08:00
Gregory Schier 84219571e8 Improved prompt function add add ctx.* functions (#301) 2025-11-15 08:19:58 -08:00
iammordaty 7ced183b11 Change wording from "Show sidebar" to "Toggle sidebar" (#300) 2025-11-13 13:40:51 -08:00
Gregor Majcen 593a7ab7e5 Add an option to allow jsonpath/xpath to return as array (#297)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-11-13 05:57:11 -08:00
Zhizhen He a4c4663011 Merge pull request #298
* fix: replace unstable from_mins to stable from_secs
2025-11-12 07:01:58 -08:00
jzhangdev 5745a96106 Merge pull request #299
* Fix scroll bar layout in EventStreamViewer
2025-11-12 06:58:35 -08:00
Gregory Schier 5449e3c620 Add sidebar action to select the active request 2025-11-11 14:38:05 -08:00
Gregory Schier 7b6278405c Focus request/folder after creation 2025-11-11 14:11:43 -08:00
goldlinker 8164a61376 chore: make some documents clearer (#276) 2025-11-10 17:25:54 -08:00
Jeroen Van den Berghe 2e9f21f838 Convert Insomnia variables syntax in headers, parameters and form data (#291)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-11-10 17:24:30 -08:00
Gregory Schier 0d725b59bd Verify trusted-signing-cli version 2025-11-10 15:02:10 -08:00
Gregory Schier 632860c29b Try again 2025-11-10 14:58:54 -08:00
Gregory Schier e1cf16f6e1 Try again 2025-11-10 14:49:15 -08:00
Gregory Schier 47c9cfb295 Fix release? 2025-11-10 14:46:09 -08:00
Gregory Schier 6389fd3b8f Connection re-use for plugin networking and beta NTLM plugin (#295) 2025-11-10 14:41:49 -08:00
Gregory Schier d318546d0c Back to vertical tabs in workspace settings 2025-11-10 06:21:26 -08:00
Gregory Schier 2f60b7b1f3 Switch trusted-signing-cli install method 2025-11-09 13:55:51 -08:00
Gregory Schier 75dc82570b Rename BadgeButton to PillButton 2025-11-09 08:18:26 -08:00
Gregory Schier d7a7a64ec4 New "Triangle" theme 2025-11-09 07:55:31 -08:00
Gregory Schier 3aae1b52d1 Update commercial use trial wording 2025-11-09 07:19:05 -08:00
Gregory Schier 9eddf716e1 Update commercial use trial wording 2025-11-09 07:07:18 -08:00
Gregory Schier 554e632c19 Minor license handling tweaks 2025-11-09 06:01:03 -08:00
Gregory Schier 054916b7af JSON linting 2025-11-08 15:24:31 -08:00
Gregory Schier f2a63087b0 Actually fix GraphQLEditor.tsx properly 2025-11-06 09:33:12 -08:00
Gregory Schier 6f0d4ad5e4 Fix GraphQL editor 2025-11-06 06:31:56 -08:00
Gregory Schier cd3530f598 Dropdown to setup sync now opens the correct workspace settings tab 2025-11-06 05:13:18 -08:00
Gregory Schier 53aea914ac Don't drag tree item when editing
https://feedback.yaak.app/p/select-text-of-navbar-in-edit-mode
2025-11-06 05:10:23 -08:00
Gregory Schier dc0c1decee Fix copy-curl with API key
https://feedback.yaak.app/p/copy-as-curl-bug-when-auth-use-api-key-with
2025-11-05 10:21:26 -08:00
Gregory Schier 32d56f2274 OAuth 1 Authentication Plugin (#292) 2025-11-05 10:12:48 -08:00
Gregory Schier ef86c1d189 Recursively collapse during "coolapse all" 2025-11-05 10:12:10 -08:00
Gregory Schier e264c50427 Show more resopnse header y height 2025-11-05 10:11:55 -08:00
Gregory Schier f05ad62301 Fix zoom hotkey
https://feedback.yaak.app/p/zoom-in-not-working-on-linux-mint
2025-11-05 10:11:46 -08:00
Gregory Schier 0a6228bf16 Fix Input ref timing, PairEditor initialization, and environment variable focus 2025-11-04 14:04:12 -08:00
Gregory Schier fa3a0b57f9 Fix Editor.tsx wonkiness 2025-11-04 13:44:18 -08:00
Gregory Schier 4390c02117 Fix gRPC message editing 2025-11-04 12:35:36 -08:00
Gregory Schier 77011176af Fix tab flexbox issue 2025-11-04 09:22:28 -08:00
Gregory Schier 759fc503d3 Fix accidental typing 2025-11-04 08:51:46 -08:00
Gregory Schier 0cb633e479 A bunch of fixes 2025-11-04 08:44:08 -08:00
Gregory Schier 81ceb981e8 Oops 2025-11-03 15:05:50 -08:00
Gregory Schier 4dae1a7955 Improve selecting items during filter 2025-11-03 15:04:02 -08:00
Gregory Schier d119f4cab2 Fix confirm with text autofocus 2025-11-03 14:42:30 -08:00
Gregory Schier 7e1eb90d29 Show error when enabling encryption fails 2025-11-03 14:34:43 -08:00
Gregory Schier bf97ea1659 Some sidebar fixes 2025-11-03 14:17:11 -08:00
Gregory Schier 749ca968ec Fix environment sorting 2025-11-03 13:53:41 -08:00
Gregory Schier 0c54b481fb Fix unused variable 2025-11-03 13:29:47 -08:00
Jeroen Van den Berghe 4943bad8ec Import query parameters from Insomnia v4 and v5 exports (#290) 2025-11-03 13:03:24 -08:00
Gregory Schier 450dbd0053 Better syntax highlighting for filter expressions 2025-11-03 06:30:41 -08:00
Gregory Schier 236c8fa656 Fix sidebar reselection after dragging non-selelected item or renaming 2025-11-03 06:19:04 -08:00
Gregory Schier 1dfc2ee602 Support encoding values to base64 (url safe) 2025-11-03 06:07:34 -08:00
Gregory Schier 1d158082f6 Pass host environment variable to plugin runtime
https://feedback.yaak.app/p/when-i-use-clash-yaak-fails-to-launch
2025-11-03 06:02:18 -08:00
Gregory Schier f3e44c53d7 Show full paths in command palette switcher
https://feedback.yaak.app/p/command-palette-search-should-include-parent-folder-names
2025-11-03 05:54:29 -08:00
Gregory Schier c8d5e7c97b Add support for API key authentication in cURL conversion
https://feedback.yaak.app/p/copy-as-curl-without-api-key
2025-11-03 05:05:54 -08:00
Gregory Schier 9bde6bbd0a More efficient editor state saves 2025-11-02 06:16:45 -08:00
Gregory Schier df5be218a5 Remove debug console logs from Input component 2025-11-02 05:52:56 -08:00
Gregory Schier 2deb870bb6 Fix pair editor 2025-11-02 05:52:36 -08:00
Gregory Schier 0f9975339c Fixes for last commit 2025-11-01 09:33:57 -07:00
Gregory Schier 6ad4e7bbb5 Click env var to edit AND improve input/editor ref handling 2025-11-01 08:39:07 -07:00
Gregory Schier 2bcf67aaa6 Fallback to jsonpath for response filter 2025-10-31 09:45:29 -07:00
Gregory Schier c01b8ce4ca Fix sort priority 2025-10-31 09:40:37 -07:00
Gregory Schier f7bb649b16 Fix ref type 2025-10-31 09:25:04 -07:00
Gregory Schier e3e67c8df7 Use TRee component for Environment dialog (#288) 2025-10-31 09:16:29 -07:00
gschier c9698c0f23 Deploying to main from @ mountain-loop/yaak@2cdd1d8136 🚀 2025-10-31 15:36:52 +00:00
Gregory Schier 2cdd1d8136 Tree fixes and sidebar filter DSL 2025-10-31 05:59:46 -07:00
gschier 8d8e5c0317 Deploying to main from @ mountain-loop/yaak@4e66a73677 🚀 2025-10-30 00:20:16 +00:00
Gregory Schier 4e66a73677 npm i 2025-10-29 15:37:46 -07:00
Gregory Schier 08f1bc4e65 Disable sidebar filtering for now 2025-10-29 15:30:18 -07:00
Gregory Schier c6d9cb9c9e Narrow vim keys selector 2025-10-29 14:59:33 -07:00
Gregory Schier efbb90dd60 Prevent vim hotkeys from activating tree in sidebar filter 2025-10-29 14:59:13 -07:00
Gregory Schier 7a7940d365 Change response history dropdown icon 2025-10-29 14:58:56 -07:00
Börge Kiss 8a6f80a181 Fix dismissable banner action button title (#273) 2025-10-29 08:16:33 -07:00
Quentin Ross e8e0097e2d Fix websocket url parameters not parsing variables (#281) 2025-10-29 08:16:07 -07:00
Zhizhen He f475b05c51 Allow specifying time for unix / unix millis / iso 8601 format (#283)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-10-29 08:15:19 -07:00
Madeleaan 7e5f9004e2 Fix text on plugin installation button (#284) 2025-10-29 08:14:40 -07:00
Gregory Schier 660771b48c Add random.range() template function 2025-10-29 08:02:12 -07:00
Gregory Schier 030e8b837e Fix incorrect Postman AWS auth key mapping and update test fixtures 2025-10-29 07:08:02 -07:00
Gregory Schier a42cba567c Support all possible Postman auth types 2025-10-29 07:06:10 -07:00
Gregory Schier 484b5b2fd8 Switch to vkbeautify for XML
https://feedback.yaak.app/p/xml-pretty-formatter-not-rendering-correctly
2025-10-28 14:03:49 -07:00
Gregory Schier a71fb8ed6c Don't trigger hotkeys within sidebar edit input 2025-10-28 13:03:37 -07:00
Gregory Schier 5b8114f6f3 Add context menu support and Vim keybindings in Sidebar and Tree components 2025-10-28 08:45:36 -07:00
Gregory Schier 68637d24c7 Don't throw on empty variable values
https://feedback.yaak.app/p/variable-with-empty-value-in-request-will-cause-error
2025-10-28 07:20:41 -07:00
Gregory Schier c097afe657 Skip disabled headers and URL parameters during rendering 2025-10-28 07:11:37 -07:00
Gregory Schier 78bc7d7909 Update label for "trialing" state to "Commercial Trial" in LicenseBadge 2025-10-28 07:11:17 -07:00
Gregory Schier b68ce44d52 Colorize HTTP methods in dropdown
https://feedback.yaak.app/p/colorized-methods-on-dropdown-select
2025-10-28 07:11:03 -07:00
Gregory Schier 632344d166 Adjust LicenseBadge color for "trialing" state to secondary 2025-10-28 07:04:16 -07:00
Gregory Schier f3814b7d2b Show cursor in response view 2025-10-28 07:03:19 -07:00
Gregory Schier 618a544dbd Adjust default font sizes for editor and interface settings 2025-10-28 07:03:06 -07:00
Gregory Schier 9a55426236 Fix incorrect Sidebar hidden state logic 2025-10-28 06:58:31 -07:00
Gregory Schier b7ad490c9b Add setting to disable checking for notifications 2025-10-28 06:55:56 -07:00
Gregory Schier 2095cb88c2 Fix entering encryption key
https://feedback.yaak.app/p/encryption-feature-error
2025-10-28 06:55:03 -07:00
Gregory Schier a9e05ae988 Copy on "type to confirm" dialog 2025-10-28 06:15:44 -07:00
Gregory Schier 99a6c38632 Sidebar filtering and improvements (#285) 2025-10-27 14:10:28 -07:00
Gregory Schier b2766509e3 Hotkey for creating environment when dialog open 2025-10-26 12:10:41 -07:00
Gregory Schier 3f5b5a397c Better environment color picker (#282) 2025-10-26 12:05:03 -07:00
Gregory Schier 923b1ac830 Fix indent guide on drag and drop after expand folder
https://feedback.yaak.app/p/displace-moving-caret-on-spring-loaded-folder
2025-10-25 09:41:06 -07:00
Gregory Schier 17dbe7c9a7 API key auth to copy-as-grpcurl 2025-10-25 08:43:50 -07:00
Gregory Schier df80cdfe33 Copy as curl AWS auth, and handle disabled auth 2025-10-25 08:33:27 -07:00
Gregory Schier eb1916b773 Fix tests 2025-10-24 15:22:20 -07:00
Gregory Schier a3df0489b1 Fix Insomnia v4 environment importer 2025-10-24 15:21:20 -07:00
Gregory Schier b19e036a61 Better CSS 2025-10-24 15:06:08 -07:00
Gregory Schier b51e37f221 Try fix folder variable pane layout 2025-10-24 14:53:07 -07:00
Gregory Schier cf9882b5b9 Fix response viewer stream scrolling 2025-10-24 14:39:25 -07:00
Gregory Schier bbf85c953d Better XML formatting, fix pointer cursor in sidebar, copy/create URL in response 2025-10-24 09:50:42 -07:00
Gregory Schier 17ddc76223 Better XML beautify 2025-10-24 08:59:16 -07:00
Gregory Schier 754ec0ba86 Fix AWS auth
https://x.com/NilsFleischer63/status/1981719735432511553
2025-10-24 08:42:18 -07:00
Gregory Schier 1198aa7d87 Add tree rename (on Enter) and global rename hotkeys (#279) 2025-10-24 08:01:38 -07:00
Gregory Schier 43437abae7 Add custom DNS resolver for *.localhost (#280) 2025-10-24 08:01:12 -07:00
moebiuscorzer 9439cfa2ba fix: typo 'validatation' corrected into 'validation' (#278) 2025-10-24 06:09:00 -07:00
gschier a731ccc8bd Deploying to main from @ mountain-loop/yaak@451c8b9dde 🚀 2025-10-23 15:36:39 +00:00
Gregory Schier 451c8b9dde Fix PDF viewer 2025-10-22 08:56:36 -07:00
Gregory Schier b7682db9a3 Remove duplicate themes in getThemes function 2025-10-22 06:56:00 -07:00
Gregory Schier 7e2d72c4e3 Fix secure() function editing 2025-10-21 20:09:56 -07:00
Gregory Schier 28bb460409 Add empty workspaces array to environment output fixture 2025-10-21 08:16:33 -07:00
Gregory Schier 56d635166b Add tsconfig.json for importer-postman-environment plugin 2025-10-21 08:08:27 -07:00
Gregory Schier f6a7257104 Text color for selected 2025-10-21 07:46:55 -07:00
Gregory Schier 1fce060ef7 Npm i 2025-10-21 07:36:21 -07:00
Gregory Schier 5c966e5a95 Add bracket matching 2025-10-21 07:27:07 -07:00
Gregory Schier 0520ef5d43 Import postman environments
https://feedback.yaak.app/p/import-postman-environments
2025-10-21 07:20:37 -07:00
dependabot[bot] 25b110778a Bump vite from 7.0.7 to 7.0.8 (#269) 2025-10-20 21:19:55 -07:00
Gregory Schier 327bf84e57 Clarify proto import buttons 2025-10-20 09:23:12 -07:00
Gregory Schier 1c48b309b5 Fix indent guide hovering 2025-10-20 09:13:00 -07:00
Gregory Schier 7c5dec821d Remove React.lazy on overlay and tooltip 2025-10-19 12:00:30 -07:00
gschier dcd8f6c08a Deploying to main from @ mountain-loop/yaak@31f9a63c3b 🚀 2025-10-19 17:19:55 +00:00
Gregory Schier 31f9a63c3b Don't force push 2025-10-19 10:18:32 -07:00
Gregory Schier e902b67a63 Replace arrayMove with custom implementation in PairEditor to remove dependency on @dnd-kit/sortable. 2025-10-19 09:40:11 -07:00
Gregory Schier b11c72fde4 Add back creation items to context menu 2025-10-19 08:52:03 -07:00
Gregory Schier 07b90c6ae3 Make plugins scrollable 2025-10-19 08:21:36 -07:00
Gregory Schier ba6163b6d8 Better code splitting and removed final instances of react-dnd 2025-10-19 08:16:56 -07:00
Gregory Schier 8055b625d0 Improve handling of drag-and-drop for collapsed and empty folders in tree component 2025-10-18 07:59:14 -07:00
Gregory Schier 3a61ffbbb0 Better drag for empty folders 2025-10-18 07:41:33 -07:00
Gregory Schier f8478677c5 Pass the previous app version to the notification endpoint so the update notification can display all missed changelogs, not just the latest one. 2025-10-18 07:13:52 -07:00
Gregory Schier f5094c5a94 Fix drop marker 2025-10-17 16:15:14 -07:00
Étienne Lévesque 8300187566 [Plugins] [Auth] [oauth2] Support identity platforms with underlying IDPs (#261)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-10-17 16:07:25 -07:00
Kien Dang cd8ab3616e Fix GraphQL doc explorer CountBadge stacking order (#262) 2025-10-17 15:33:40 -07:00
Maksim Karelov be0c92b755 Add ability to select fs.readFile encoding (#267) 2025-10-17 15:32:04 -07:00
Gregory Schier c34ea20406 Flattened the sidebar tree 2025-10-17 15:07:02 -07:00
Gregory Schier 6e9b1db196 Bump version 2025-10-16 14:42:02 -07:00
Gregory Schier d83aabd2be Dynamic template function args and TTL option for request chaining (#266) 2025-10-16 14:39:30 -07:00
Gregory Schier d46479cd22 Remove debug console log from TreeDragOverlay component 2025-10-15 14:08:21 -07:00
Gregory Schier 19cae33382 Fix crash when delete after drag 2025-10-15 14:07:55 -07:00
Gregory Schier 267cd079ad New sidebar and folder view (#263) 2025-10-15 13:46:57 -07:00
Gregory Schier 19c1efc73e Resolve 2025-10-11 08:28:07 -07:00
Gregory Schier dfa9a22861 Merge remote-tracking branch 'origin/main' 2025-10-11 06:29:17 -07:00
Gregory Schier 533f9bacc4 Add AWS authentication 2025-10-11 06:29:06 -07:00
Zhizhen He 0358748729 Fix icon paths in package.json (#265) 2025-10-09 04:24:44 -07:00
Zhizhen He 1540d0a5a5 Fix typo (#264) 2025-10-08 19:54:18 -07:00
Gregory Schier d177e164f1 Fix log 2025-10-08 04:25:06 -07:00
Gregory Schier f1355c9d15 Fix non-release build 2025-10-08 04:25:00 -07:00
Gregory Schier 485a9ea47c Show toast on plugin event handling errors instead of crashing
Also set folder context on template render and fix timestamp function
2025-10-06 06:53:45 -07:00
Gregory Schier dbc606fb53 Update README 2025-10-04 08:22:39 -07:00
Gregory Schier a00b4ae232 Always open new window when clicking current workspace:
https://feedback.yaak.app/p/dont-trigger-workspace-change-when-clicking-same-workspace
2025-10-04 06:47:50 -07:00
Gregory Schier 998b5cf78a Add RenderOptions and RenderErrorBehavior to ensure auth UI still loads with missing variables 2025-10-04 06:29:29 -07:00
Gregory Schier b4deae6e8d Batch insert environments last, to handle folder case 2025-10-04 05:48:08 -07:00
Gregory Schier 87fdf17010 Fix plugin refresh toasts having no timeout 2025-10-04 05:47:39 -07:00
Gregory Schier c6975a9e8b Allow toast interaction when dialog is open 2025-10-04 05:47:16 -07:00
Gregory Schier b44ac55bc2 Fix broken environment migration 2025-10-04 05:47:00 -07:00
Gregory Schier 9c65c95ba9 Fix batch import potentially creating a useless base environment 2025-10-04 05:46:42 -07:00
Gregory Schier 7beb9f4e69 Merge remote-tracking branch 'origin/main' 2025-10-04 05:46:13 -07:00
Gregory Schier dbecd74f46 Allow selecting confirm text 2025-10-04 05:46:06 -07:00
Gregory Schier 6826ee1672 Revise README for clarity and updated features
Updated the README to enhance clarity and organization, including feature descriptions and removing outdated content.
2025-10-03 10:29:19 -07:00
Gregory Schier a12ae7ef56 Update README 2025-10-03 09:53:40 -07:00
gschier dbc100409d Deploying to main from @ mountain-loop/yaak@6b87cd9655 🚀 2025-10-03 16:52:47 +00:00
Gregory Schier 6b87cd9655 Merge remote-tracking branch 'origin/main' 2025-10-03 09:52:31 -07:00
Gregory Schier 7ce2cdc9cc Update tiers 2025-10-03 09:52:26 -07:00
gschier 1f4e38b7a7 Deploying to main from @ mountain-loop/yaak@0013a0797b 🚀 2025-10-03 16:50:09 +00:00
Gregory Schier 0013a0797b Merge remote-tracking branch 'origin/main' 2025-10-03 09:49:45 -07:00
Gregory Schier 5e9b14dc0b Update workflow 2025-10-03 09:49:39 -07:00
gschier b7cfb0db13 Deploying to main from @ mountain-loop/yaak@8948bfbf45 🚀 2025-10-03 16:46:35 +00:00
Gregory Schier 8948bfbf45 Merge remote-tracking branch 'origin/main' 2025-10-03 09:46:19 -07:00
Gregory Schier 4218e90bf4 Set active-only to true in sponsors workflow 2025-10-03 09:44:56 -07:00
gschier 2172d7ac60 Deploying to main from @ mountain-loop/yaak@5e45cb4908 🚀 2025-10-03 16:42:45 +00:00
Gregory Schier 5e45cb4908 Update sponsors workflow to use SPONSORS_PAT secret 2025-10-03 09:42:21 -07:00
Gregory Schier d662883fdd Update README 2025-10-03 09:41:17 -07:00
Gregory Schier f83f3d4682 Refine Yaak description in README
Updated the description of Yaak in the README.
2025-10-03 09:36:22 -07:00
gschier e03c745093 Deploying to main from @ mountain-loop/yaak@73b9d699ed 🚀 2025-10-03 16:35:10 +00:00
Gregory Schier 73b9d699ed Merge remote-tracking branch 'origin/main' 2025-10-03 09:34:35 -07:00
Gregory Schier 5a7b9aba2f Sponsors workflow 2025-10-03 09:34:22 -07:00
Gregory Schier cf433b26a5 Revise sponsor section in README.md
Updated sponsor section in README.md to include new sponsor images and links.
2025-10-03 09:32:36 -07:00
gschier 573035b17d Deploying to main from @ mountain-loop/yaak@3844fec968 🚀 2025-10-03 16:31:42 +00:00
Gregory Schier a267c0c53f Update README 2025-10-03 09:31:21 -07:00
gschier 328563f4e6 Deploying to main from @ mountain-loop/yaak@3844fec968 🚀 2025-10-03 16:30:09 +00:00
Gregory Schier 3844fec968 Merge remote-tracking branch 'origin/main' 2025-10-03 09:29:35 -07:00
Gregory Schier 8557a2477b Sponsors workflow 2025-10-03 09:29:31 -07:00
Gregory Schier d02519ab74 Update README header 2025-10-03 09:19:17 -07:00
Gregory Schier 1a1751c23e Fix window path issue 2025-10-02 08:25:00 -07:00
Gregory Schier 17de0678b0 Remove unused import from window.rs 2025-10-02 08:05:07 -07:00
Gregory Schier 20bb89de33 Try fix oauth window creation 2025-10-02 07:45:50 -07:00
Gregory Schier 8a634b1056 Add back environment.base (#260) 2025-10-02 06:04:27 -07:00
Gregory Schier 57f231ca00 Add trial status to links 2025-10-01 21:14:26 -07:00
Gregory Schier cb1c0e4d8c Fix ref 2025-10-01 21:07:44 -07:00
Gregory Schier 2152cf87d7 Tweak license badge and fix keyring dep 2025-10-01 21:01:27 -07:00
Gregory Schier 8662b230e7 Oops, actually fix 2025-10-01 16:54:05 -07:00
Gregory Schier 3a8a6484c7 Fix release windows signing 2025-10-01 16:42:38 -07:00
Gregory Schier f92594a16d Fix release tauri config 2025-10-01 10:51:26 -07:00
Gregory Schier 7969fcb76c Alias keyring function 2025-10-01 10:22:06 -07:00
Gregory Schier eafefb1894 Fix setting 2025-10-01 09:44:18 -07:00
Gregory Schier 9a94a15c82 Integrated update experience (#259) 2025-10-01 09:36:36 -07:00
Gregory Schier 757d28c235 License and updater Cargo features (#258) 2025-09-29 22:08:05 -07:00
Gregory Schier 6c79c1ef3f Rework licensing flows to be more friendly 2025-09-29 15:40:15 -07:00
Gregory Schier 7262eccac5 Fix keyring errors 2025-09-29 10:53:20 -07:00
Gregory Schier 4989a5f759 Add back cmd palette icon 2025-09-29 09:39:15 -07:00
Gregory Schier 0b0b05d29c Add keyring template function 2025-09-29 08:56:24 -07:00
Gregory Schier b3d6d87bee Delete duplicate folder environments on upsert 2025-09-29 07:48:07 -07:00
Gregory Schier 6abbdc8726 Filter out current variable from autocomplete and show sub environment variables in base environment autocomplete 2025-09-29 06:57:04 -07:00
Gregory Schier b9613591f8 Update resource links in README.md 2025-09-28 15:38:14 -07:00
Gregory Schier eb555989ac Force grpcurl to posix paths 2025-09-25 08:40:57 -07:00
Gregory Schier b77f1375fd Fix test with timezone 2025-09-25 08:03:07 -07:00
Gregory Schier 3c438b3da7 Add cmdctrl+Backspace for request delete 2025-09-25 07:28:01 -07:00
Gregory Schier df15543c80 Explicitly set the request layout (#257) 2025-09-25 07:23:52 -07:00
Gregory Schier 73ad86c6b9 Fix workspace settings scroll with long description 2025-09-25 07:22:42 -07:00
Gregory Schier 615de8b3cc Update importers for folder environment and fix tests 2025-09-25 07:12:50 -07:00
Gregory Schier 2418bd0672 Update README.md 2025-09-24 11:03:23 -07:00
Gregory Schier b3414ee60f Fix ephemeral response body reading 2025-09-22 14:07:25 -07:00
Gregory Schier 8fe50959b9 Add migrate for base environment to sync logic 2025-09-22 11:15:32 -07:00
Gregory Schier 523e7dcf16 Add bootstrap to release script (to fix lint) 2025-09-22 08:57:05 -07:00
Gregory Schier 7951f3a7bd Tweak light theme, high contrast themes, and fix env null reference 2025-09-22 08:36:40 -07:00
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
Gregory Schier a258a80fbd Prevent auth from adding lone ? to URL
https://feedback.yaak.app/p/using-inherited-api-key-causes-a-question-mark-to-be
2025-07-23 17:20:17 -07:00
Gregory Schier 1b90842d30 Regex template function 2025-07-23 13:33:58 -07:00
Carter Costic f1acb3c925 Merge pull request #245
* Attach cookies to WS Upgrade

* Merge branch 'main' into main

* Move reqwest_cookie_store to workspace dep
2025-07-23 13:14:15 -07:00
Gregory Schier 28630bbb6c Remove template as default value 2025-07-23 12:46:26 -07:00
Gregory Schier 86a09642e7 Rename template-function-datetime 2025-07-23 12:42:54 -07:00
Song 0b38948826 add template-function-datetime (#244) 2025-07-23 12:41:24 -07:00
Gregory Schier c09083ddec Fix up export dialog 2025-07-21 14:45:13 -07:00
Gregory Schier 44ee020383 Plugins menu item and link to run button 2025-07-21 14:38:29 -07:00
Gregory Schier c609d0ff0c Fix GraphQL schema getting nuked on codemirror language refresh 2025-07-21 14:17:36 -07:00
Gregory Schier 7eb3f123c6 Add run button link 2025-07-21 07:47:29 -07:00
Gregory Schier 2bd8a50df4 Tweak tab padding 2025-07-21 07:45:11 -07:00
Gregory Schier 178cc88efb Fix Authenticatin typo
https://feedback.yaak.app/p/authentication-misspelled-in-request-auth-tooltip
2025-07-21 07:39:54 -07:00
Gregory Schier 38b2893cbf npm i 2025-07-20 09:48:57 -07:00
Gregory Schier 144faad31f Add API key auth
https://feedback.yaak.app/p/header-as-auth-option
2025-07-20 09:15:03 -07:00
Gregory Schier 947926ca34 Fix deadlock 2025-07-20 08:58:22 -07:00
Gregory Schier 86f23990eb Fixed bugs in Plugin settings pane 2025-07-20 08:28:00 -07:00
Gregory Schier 861b41b5ae JSONPath plugin README 2025-07-20 06:42:33 -07:00
Gregory Schier 7f4ccbe014 OAuth 2 plugin README 2025-07-19 21:47:19 -07:00
Gregory Schier 3b61c836be Merge remote-tracking branch 'origin/main' 2025-07-19 21:39:47 -07:00
Gregory Schier 6616cb67cd JWT plugin README 2025-07-19 21:39:40 -07:00
Song e5fd4134ba inline url search param and use --data (#239) 2025-07-19 21:28:39 -07:00
Gregory Schier 31b0b14c04 Merge remote-tracking branch 'origin/main' 2025-07-19 21:25:21 -07:00
Gregory Schier daeaf2a999 Bearer plugin README 2025-07-19 21:25:15 -07:00
Song ca2fe07265 Optimize request function (#242)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-07-19 09:29:42 -07:00
Song adca071574 fix padding and hover highlight in tabs (#243) 2025-07-19 09:19:48 -07:00
Gregory Schier d6057aa1ec Basic auth plugin README 2025-07-19 09:15:06 -07:00
Gregory Schier 60883cc1b9 copy grpcurl readme and fix 2025-07-19 09:10:49 -07:00
Gregory Schier b32fe466b1 Copy as curl readme 2025-07-19 07:38:46 -07:00
Gregory Schier f81ff27a9e Don't wrap tab content 2025-07-18 14:52:19 -07:00
Gregory Schier 8f737d799b Pad dynamic form for scrollbar 2025-07-18 14:52:08 -07:00
Gregory Schier b67ea29aff Better error 2025-07-18 14:49:13 -07:00
Gregory Schier a657c32445 Better authorization URL handling 2025-07-18 14:48:45 -07:00
Andrew Berezovskyi 5061e17700 Update mimetypes.ts with RDF mime types beyond JSON-LD and N3 (#235) 2025-07-18 14:37:14 -07:00
Song d9d5c4d564 remove unnecessary semicolon in tailwind config file (#236) 2025-07-18 14:36:28 -07:00
Song 343986c018 make monospace font family follows app setting in auto completion menu (#237)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-07-18 14:35:57 -07:00
Song 0d4b7bb5e2 Improve <details> component (#238)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-07-18 14:28:24 -07:00
Song 4a2fb6ed48 Improve layout resizer (#240)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-07-18 13:35:29 -07:00
Gregory Schier 74b6f4fb42 Fix pair editor creating new entry by clicking value 2025-07-18 08:54:37 -07:00
Gregory Schier bcde4de4a7 Tweak workspace settings and a bunch of small things 2025-07-18 08:47:14 -07:00
Gregory Schier 4c375ed3e9 Tweak 2025-07-15 07:25:34 -07:00
Gregory Schier 2fcd2a3c07 Fix docs explorer cmd+click 2025-07-15 07:02:08 -07:00
Gregory Schier 0c60d190af Fix lint errors and show docs explorer on Cmd click 2025-07-14 14:52:16 -07:00
Gregory Schier 6f1fd7a254 Fix lint errors after upgrades and narrow tsc 2025-07-14 10:09:08 -07:00
Gregory Schier 5c1fba4b0c Fix Postman import description
https://feedback.yaak.app/p/missing-documentation-info-when-importing-postman-requests
2025-07-14 07:36:04 -07:00
Gregory Schier 6df13c452b Upgrade dependencies 2025-07-14 07:35:37 -07:00
Gregory Schier 209ac45ed2 Fix pop out scroll 2025-07-11 08:52:31 -07:00
Gregory Schier ad4e073f62 Pop out dynamic form editor into dialog 2025-07-11 08:33:04 -07:00
Gregory Schier 791e5ad486 Fixes for websocket closing 2025-07-11 08:10:14 -07:00
Gregory Schier fef6cc47f9 Smaller cancel button 2025-07-10 14:37:32 -07:00
Gregory Schier c94331f454 Support GET GraphQL queries
https://feedback.yaak.app/p/support-get-graphql-queries-out-of-the-box
2025-07-10 14:06:54 -07:00
Gregory Schier a31f818424 Don't show plugin error for response filter
https://feedback.yaak.app/p/increase-debounce-time-for-jsonpath-xpath-filter
https://feedback.yaak.app/p/possibility-to-cancel-request
2025-07-10 13:49:53 -07:00
Gregory Schier f63da432b9 Fix split in curl importer
https://feedback.yaak.app/p/import-from-curl-does-not-work-properly-sometimes
2025-07-10 13:13:28 -07:00
Gregory Schier 456c8bd95f Add env key to useRenderTemplate()
https://feedback.yaak.app/p/environment-preview-is-inaccurate
2025-07-10 13:06:00 -07:00
Gregory Schier b529bab578 Lower large response confirm 2025-07-10 12:59:45 -07:00
Gregory Schier 840f15c997 Always update response if error
https://feedback.yaak.app/p/cant-re-send-request-if-there-is-one-ongoing
2025-07-10 12:51:04 -07:00
Gregory Schier f745435d26 Add comment 2025-07-10 11:47:26 -07:00
Gregory Schier 4038666986 Update single line filter extension 2025-07-10 11:46:27 -07:00
mooonfly 2b07d1a493 Fix duplicated character when composing text (#234) 2025-07-10 11:37:29 -07:00
Gregory Schier 333b64e7f3 Resolve requests for request actions
https://feedback.yaak.app/p/plugin-cannot-get-inhereted-parameters-when-rendering-a-request
2025-07-10 11:32:03 -07:00
Gregory Schier 9cd430b3de Docs explorer tweaks 2025-07-10 06:35:52 -07:00
Gregory Schier f0bafb21cc Fix 2025-07-09 14:25:11 -07:00
Gregory Schier f00adf6fce A bunch of responsiveness fixes 2025-07-09 14:24:29 -07:00
Gregory Schier d9f9ea4047 Fix state bug 2025-07-09 12:48:40 -07:00
Gregory Schier 036e85d006 Schema filtering and a bunch of fixes 2025-07-09 12:39:27 -07:00
Gregory Schier a03ec8875c Persist gql docs shown state 2025-07-08 09:29:56 -07:00
Gregory Schier a3f50a2bb7 Clean up GraphQL explorer 2025-07-08 07:44:50 -07:00
Gregory Schier 6c0f9377cd Fix plugin builds 2025-07-07 14:17:47 -07:00
Gregory Schier bd2662fbe3 Show implements and fix non-null and list types 2025-07-07 14:12:28 -07:00
Gregory Schier f5dbff4682 Add docs close button 2025-07-07 13:59:06 -07:00
Gregory Schier 7a11da42af Some fixes 2025-07-07 13:52:54 -07:00
Gregory Schier 01f9c072a7 I think we're good 2025-07-07 13:41:26 -07:00
Gregory Schier 47722643ee Add descriptions to plugins 2025-07-06 12:47:13 -07:00
Gregory Schier cf35658fea Revert Tauri CLI 2025-07-05 16:45:07 -07:00
Gregory Schier 6330c77948 Fix linux build 2025-07-05 16:16:50 -07:00
Gregory Schier 77d2edd947 Add log 2025-07-05 16:00:46 -07:00
Gregory Schier 4f0f60cb99 Add log 2025-07-05 16:00:20 -07:00
Gregory Schier dd2b665982 Tweak protos CLI arg generation 2025-07-05 15:58:36 -07:00
Gregory Schier 19ffcd18a6 gRPC request actions and "copy as gRPCurl" (#232) 2025-07-05 15:40:41 -07:00
Gregory Schier ad4d6d9720 Merge branch 'theme-plugins'
# Conflicts:
#	packages/plugin-runtime-types/src/bindings/gen_events.ts
2025-07-05 06:37:32 -07:00
Gregory Schier 9e98b5f905 Fix macos window theme calculation 2025-07-05 06:37:02 -07:00
Gregory Schier 19c6ad9d97 Theme plugins (#231) 2025-07-03 13:06:30 -07:00
Gregory Schier a0e5e60803 Fix filter plugin names 2025-07-03 12:28:34 -07:00
Gregory Schier 2a6f139d36 Better plugin reloading and theme parsing 2025-07-03 12:25:22 -07:00
Gregory Schier 36bbb87a5e Mostly working 2025-07-03 11:48:17 -07:00
Gregory Schier a6979cf37e Print table/col/val when row not found 2025-07-02 08:14:52 -07:00
Gregory Schier ff26cc1344 Tweaks 2025-07-02 07:47:36 -07:00
Gregory Schier fa62f88fa4 Allow moving requests and folders to end of list 2025-06-29 08:40:14 -07:00
Gregory Schier 99975c3223 Fix sidebar folder dragging collapse
https://feedback.yaak.app/p/a-folder-may-hide-its-content-if-i-move-a
2025-06-29 08:02:55 -07:00
Gregory Schier d3cda19be2 Hide large request bodies by default 2025-06-29 07:30:07 -07:00
Gregory Schier 9b0a767ac8 Prevent command palette from jumping with less results 2025-06-28 07:37:15 -07:00
Gregory Schier 81c3de807d Add json.minify 2025-06-28 07:29:24 -07:00
Gregory Schier 9ab02130b0 Fix sync import issues:
https://feedback.yaak.app/p/yaml-error-missing-field-type-at-line-4521-column-1
2025-06-27 13:32:52 -07:00
Gregory Schier 25d50246c0 Revert notification endpoint URL for dev 2025-06-27 11:58:04 -07:00
Gregory Schier bb0cc16a70 Use API client for notifications/license 2025-06-25 08:17:17 -07:00
Gregory Schier 8817be679b Fix PKCE flow and clean up other flows 2025-06-25 07:10:11 -07:00
Gregory Schier f476d87613 Add back unsigned memory entitlement 2025-06-24 06:21:07 -07:00
Gregory Schier 1438e8bacc Upgrade eslint and fix issues 2025-06-23 14:09:09 -07:00
Gregory Schier 7be2767527 Fix lint error 2025-06-23 09:51:44 -07:00
Gregory Schier a1b1eafd39 Add links to plugins 2025-06-23 09:46:54 -07:00
Gregory Schier 1948fb78bd Fix bad import 2025-06-23 08:57:31 -07:00
Gregory Schier cb7c44cc65 Install plugins from Yaak plugin registry (#230) 2025-06-23 08:55:38 -07:00
Gregory Schier b5620fcdf3 Merge pull request #227
* Search and install plugins PoC

* Checksum

* Tab sidebar for settings

* Fix nested tabs, and tweaks

* Table for plugin results

* Deep links working

* Focus window during deep links

* Merge branch 'master' into plugin-directory

* More stuff
2025-06-22 07:06:43 -07:00
Mr0Bread b8e6dbc7c7 GraphQL Documentation explorer (#208) 2025-06-17 17:08:39 -07:00
Gregory Schier aadfbfdfca Fix lint errors 2025-06-10 08:16:02 -07:00
Gregory Schier 383fd05c6c Split appearance settings into theme/interface 2025-06-09 21:19:44 -07:00
Gregory Schier be0a8fc27a Add proxy bypass setting and rewrite proxy logic 2025-06-09 14:29:12 -07:00
Gregory Schier 648a1ac53c Update DEVELOPMENT.md 2025-06-08 22:49:43 -07:00
Gregory Schier 9fab37fb17 Custom font selection (#226) 2025-06-08 22:48:27 -07:00
Gregory Schier e0aaa33ccb Update README 2025-06-08 08:17:11 -07:00
Gregory Schier 20f7d20031 Enable socks reqwest feature 2025-06-08 08:10:55 -07:00
Gregory Schier 4d90bc78b1 Link docs in readme 2025-06-08 08:05:59 -07:00
Gregory Schier 97763a1301 Add README to types package 2025-06-08 08:03:11 -07:00
Gregory Schier d8b5a201b6 I'm stupid 2025-06-07 20:17:28 -07:00
Gregory Schier 88e87a1999 Fix stupid typo 2025-06-07 20:15:32 -07:00
Gregory Schier 2c4c1abd20 Pin tauri cli 2025-06-07 20:04:04 -07:00
Gregory Schier 67026fc5b3 Tweak 2025-06-07 19:37:28 -07:00
Gregory Schier 423a1a0a52 Fix environment color editing 2025-06-07 19:32:23 -07:00
Gregory Schier 1abe01aa5a Embed migrations into Rust binary 2025-06-07 19:25:36 -07:00
Gregory Schier d0fde99b1c Environment colors (#225) 2025-06-07 18:21:54 -07:00
Gregory Schier 27901231dc Clarify proxy HTTP/HTTPS setting 2025-06-06 20:34:23 -07:00
Gregory Schier 1d9d80319b Upgrade dependencies 2025-06-06 19:32:25 -07:00
Gregory Schier f62e90297d Fix recent workspaces when open in new window 2025-06-06 14:10:30 -07:00
Gregory Schier fcda6f8d32 Fix lint errors 2025-06-04 11:33:10 -07:00
Gregory Schier 021f2171d6 Show error dialog on migration failure 2025-06-04 11:20:28 -07:00
Gregory Schier 2562cf7c55 Setting to colorize HTTP methods
https://feedback.yaak.app/p/support-colors-for-http-method-in-sidebar
2025-06-04 10:59:40 -07:00
Gregory Schier 58873ea606 Fix text streaming breaking scroll 2025-06-04 10:38:55 -07:00
Gregory Schier 9d9e83d59f Remove sqlx for migrations (#224) 2025-06-03 14:42:32 -07:00
Gregory Schier 01d40f5b0d Fix context_id for Workspace/Folder auth 2025-06-03 13:08:48 -07:00
Gregory Schier bdb1adcce1 Fix TS type 2025-06-03 12:44:14 -07:00
Gregory Schier 9f6a3da8d3 Disable auth for OAuth token http requests 2025-06-03 12:42:31 -07:00
Gregory Schier 158487e3a6 sqlx log DEBUG to debug failed migrations 2025-06-03 11:01:51 -07:00
Gregory Schier c1b18105b5 Fix log toast 2025-06-03 09:33:03 -07:00
James Cleverley-Prance eb5ef7d7d5 fix: send id_token in OAuth2 requests (#223) 2025-06-03 09:28:56 -07:00
Gregory Schier 6eb16afd96 Add debug logs for oauth plugin 2025-06-03 09:27:54 -07:00
Gregory Schier 9e68e276a1 Fix plugin runtime not quitting on cmd+Q
Related https://github.com/tauri-apps/tauri/issues/9198
2025-06-01 07:46:31 -07:00
Gregory Schier af230a8f45 Separate model for GQL introspection data (#222) 2025-06-01 06:56:00 -07:00
Gregory Schier f9ac36caf0 SyncState migration to include sync_dir in unique index:
https://feedback.yaak.app/p/after-setting-up-sync-to-folder-there-is-a-yaml
2025-05-31 12:56:13 -07:00
Gregory Schier a7a301ceba Add JSON language check 2025-05-30 10:02:43 -07:00
Gregory Schier 4166daf0a2 Hide escape character for forward slash in JSON 2025-05-30 10:00:17 -07:00
Gregory Schier b52570bf58 Support id_token for OAuth 2.0
https://feedback.yaak.app/p/unable-to-use-idtoken-for-auth-in-authorization-code-oauth2
2025-05-30 08:02:29 -07:00
Gregory Schier 1e27e1d8cb Remove .idea 2025-05-29 21:45:58 -07:00
Gregory Schier 7047260697 Remove yaakapp/cli from release CI 2025-05-29 21:44:41 -07:00
John D. Chancey fa33a89b63 fix: add missing yaakcli to dev dependencies (#221) 2025-05-29 15:57:13 -07:00
dependabot[bot] 00c0884616 Bump jsonpath-plus from 9.0.0 to 10.3.0 (#220)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-29 08:05:45 -07:00
Gregory Schier a70768b61d Merge remote-tracking branch 'origin/master' 2025-05-29 08:03:06 -07:00
Gregory Schier 8e3826b6c3 Delete unneeded 2025-05-29 08:02:57 -07:00
Gregory Schier 101efdd512 Merge plugins repo into mono 2025-05-29 08:02:24 -07:00
Gregory Schier 723e8d2874 Move everything into subdir for repo merge 2025-05-29 07:16:39 -07:00
John D. Chancey 385a369699 Fix: Add yaakcli to dev dependencies (#9) 2025-05-29 07:07:59 -07:00
James Cleverley-Prance 79362c81e5 fix: oauth2 audience not sent (#10) 2025-05-29 07:06:24 -07:00
Andy Bao bd1986f31f Fix "Validate TLS Certificates" option for WS and GRPC (#218) 2025-05-29 07:02:27 -07:00
Gregory Schier 085b640b3c Update plugins 2025-05-28 14:07:00 -07:00
Gregory Schier d07272003b Fix JSONPath function quoting strings 2025-05-28 14:06:17 -07:00
Gregory Schier bbf2b6dec0 Remove console.log 2025-05-28 13:14:46 -07:00
Gregory Schier 399cd35b2b Don't return "undefined" when no XPath match 2025-05-28 13:14:32 -07:00
Gregory Schier 72dd768f55 Proper handling of boolean template function args 2025-05-28 13:08:43 -07:00
Gregory Schier 053cbe49f9 UUID, json/x path 2025-05-28 13:07:29 -07:00
Gregory Schier 862d85e48d Better inheritance empty state 2025-05-28 10:42:57 -07:00
Gregory Schier a6d03cbeeb Fix context menu closing immediately when using ctrl+click
https://feedback.yaak.app/p/right-click-on-mac-automatically-closes
2025-05-28 07:36:18 -07:00
Gregory Schier 7d1ca1c232 Render inherited auth and headers (#217) 2025-05-26 07:18:57 -07:00
Gregory Schier 261911b57e Fix weird import 2025-05-25 20:45:12 -07:00
Gregory Schier 245054cd7d Move react-pdf dynamic import 2025-05-25 20:39:14 -07:00
Gregory Schier 21b9e5a02b Url encode/decode functions 2025-05-25 20:34:05 -07:00
Gregory Schier 6d6012fe67 More template functions 2025-05-25 20:27:04 -07:00
Gregory Schier 101582e540 Merge remote-tracking branch 'origin/master' 2025-05-25 20:25:22 -07:00
Gregory Schier 0a932798a0 API support for added template functions (eg. cookies) 2025-05-25 20:25:13 -07:00
Gregory Schier 4609c95ad5 Fix env editor switching (#216) 2025-05-25 08:03:29 -07:00
Gregory Schier 9d54e40aa8 Add list/get cookie plugin APIs 2025-05-25 08:02:36 -07:00
Gregory Schier 9ec9222216 Fix cookie jar not updating during chained requests
https://feedback.yaak.app/p/request-chaining-cookie-not-appear
2025-05-25 07:04:40 -07:00
Gregory Schier 4d1dda0786 Fix auth none 2025-05-23 08:43:52 -07:00
Gregory Schier 31605881ac Render inherited headers in UI 2025-05-23 08:18:29 -07:00
Gregory Schier 4cd2e9cd31 Request Inheritance (#209) 2025-05-23 08:13:25 -07:00
nguyen 13d959799a fix: prevent button stealing focus from url input (#212) 2025-05-23 08:12:06 -07:00
Pannawich Lohanimit a6b18c23e1 fix: change incorrect default GraphQL request name (#213) 2025-05-23 08:11:16 -07:00
Gregory Schier 041298b3f8 Detect JSON language if application/javascript returns JSON 2025-05-21 11:05:20 -07:00
Gregory Schier b400940f0e Fix import curl 2025-05-21 11:04:57 -07:00
Gregory Schier 2e144f064d Fix syntax highlighting 2025-05-21 08:26:15 -07:00
Gregory Schier d8b1cadae6 Fix model deletion 2025-05-21 08:25:12 -07:00
Gregory Schier c2f9760d08 Fix template parsing 2025-05-21 08:18:09 -07:00
Gregory Schier a4c600cb48 Lint errors 2025-05-20 08:15:19 -07:00
Gregory Schier bc3a5e3e58 Include license status in notification endpoint 2025-05-20 08:13:57 -07:00
Gregory Schier d02883282f Merge remote-tracking branch 'origin/main' 2025-05-20 08:09:11 -07:00
Gregory Schier 2c3fb25932 Fix Insomnia v5 importer 2025-05-20 08:08:56 -07:00
Gregory Schier 4c3a02ac53 Show decrypt error in secure input 2025-05-20 07:41:32 -07:00
Gregory Schier 1974d61aa4 Fix syntax highlighting 2025-05-19 15:41:19 -07:00
Gregory Schier 0bcb092854 Update README.md 2025-05-19 15:10:56 -07:00
Gregory Schier 409620f533 More advanced template grammar
Fixes https://feedback.yaak.app/p/cannot-escape-call-to-variable-in-json-body
2025-05-19 13:37:12 -07:00
Étienne Lévesque 4ae7f99264 fix: Fixes the implicit OAuth flow not waiting for user to authenticate (#8) 2025-05-17 13:47:24 -07:00
Gregory Schier 3e9037f70a No longer mark environments as external in Git 2025-05-17 06:06:36 -07:00
Desperate Necromancer be82b67ed3 Allow disabling window decorations/controls (#176)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-05-16 13:33:59 -07:00
Gregory Schier 432b366105 Fix grpc/ws events error 2025-05-16 13:00:50 -07:00
Hao Xiang 42e70b941d fix proto to json-schema (#194) 2025-05-16 12:53:53 -07:00
Gregory Schier 3808215210 Better unicode un-escaping 2025-05-16 12:42:08 -07:00
Walyson G Oliveira 763a60982a Adjusting the JSON viewing response to accept accentuation (#203) 2025-05-16 12:37:00 -07:00
dependabot[bot] a05679fd93 Bump vite from 6.2.6 to 6.2.7 in /src-web (#205)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-16 12:31:46 -07:00
Gregory Schier 73c366dc27 Hopefully fix weird env routing issue 2025-05-16 09:12:36 -07:00
Gregory Schier c73f0b02bd print body in OAuth 2 http errors 2025-05-16 08:16:18 -07:00
Gregory Schier 0be7d0283b Add ref=<app_id> to external links pointing to yaak.app 2025-05-16 07:53:22 -07:00
Gregory Schier 9615d3e29b Add audience parameter to OAuth 2
Closes https://feedback.yaak.app/p/how-do-i-send-an-audience-using-oauth2
2025-05-16 07:17:22 -07:00
Gregory Schier 749df338c5 Disable wasm-opt 2025-05-15 14:29:29 -07:00
Gregory Schier 3184c1b79e Remove dynamic imports 2025-05-15 12:29:45 -07:00
Gregory Schier b52bf7cd56 Fix HttpResponse AnyModel deserialization 2025-05-15 09:37:05 -07:00
Gregory Schier d962d7f94b remove codemirror dep and restructure a bit 2025-05-15 09:28:14 -07:00
Gregory Schier 21e2a67c1e Fix sidebar scroll drag
https://feedback.yaak.app/p/endpoinst-scrollbar-not-clickable
2025-05-15 07:10:08 -07:00
Gregory Schier c188435524 Fix obscured text overflow
https://feedback.yaak.app/p/pasting-token-auth-results-in-invisible-text
2025-05-15 07:00:25 -07:00
Gregory Schier 8a7a7ba49d Try fixing trusted-signing-cli 2025-05-14 20:43:59 -07:00
Gregory Schier cbc40230bb Fix cursor position after variable on Linux
Closes https://feedback.yaak.app/p/editing-the-url-sometimes-freezes-the-app
2025-05-14 20:05:04 -07:00
Gregory Schier bc4c3178c9 Add Content-Length: 0 default for post/put/patch
https://feedback.yaak.app/p/missing-content-length
2025-05-13 21:58:00 -07:00
Gregory Schier 121fe5b3ea Fix help text 2025-05-13 11:53:46 -07:00
Gregory Schier 861609ddc0 Update encryption help 2025-05-13 11:38:02 -07:00
Gregory Schier e5070513ac Regenerate types 2025-05-13 10:45:41 -07:00
Gregory Schier f5c3798df9 Ability to disable proxy config
Closes https://feedback.yaak.app/p/proxy-save-last-data
2025-05-13 10:35:02 -07:00
Gregory Schier 469d12fede Don't query KeyValue.id == NULL 2025-05-13 10:11:24 -07:00
Gregory Schier 417a02744b Don't select <Input/> text when focus is due to window focus
Closes https://feedback.yaak.app/p/url-input-auto-selects-all-text-when-regaining-focus-after
2025-05-12 22:19:16 -07:00
Gregory Schier 81e78ef24c Fix auth padding 2025-05-12 16:57:43 -07:00
Gregory Schier dad9cebb9e Don't send empty ? for ws query params 2025-05-12 16:57:13 -07:00
Gregory Schier b3ede3d6d6 Add error boundaries 2025-05-12 15:53:21 -07:00
Gregory Schier 035fe54df0 Send grpc metadata/auth with reflection requests
Closes https://feedback.yaak.app/p/send-metadata-during-grpc-reflection
2025-05-11 07:20:57 -07:00
Gregory Schier 5f8d99ba64 Build plugins 2025-05-11 06:46:51 -07:00
Gregory Schier 8c0f889dd2 Insomnia v5 importer (#7)
Add support for the new Insomnia 5 export format
2025-05-11 06:44:54 -07:00
Gregory Schier 84b8d130dc Some small tweaks for plugins 2025-05-11 06:36:50 -07:00
Gregory Schier 20b0b4fb69 Add test 2025-05-11 06:31:21 -07:00
mooonfly 8be9c4c388 fix curl import params (#6) 2025-05-11 06:22:36 -07:00
Gregory Schier a5333deb71 Logic for new Environment.base field 2025-05-08 14:28:41 -07:00
Gregory Schier 94d4227bc1 Ability to sync environments to folder (#207) 2025-05-08 14:10:07 -07:00
Gregory Schier 77cdea2f9f Merge remote-tracking branch 'origin/master' 2025-05-08 08:02:32 -07:00
Gregory Schier 8b1ca4cb47 Fix copy response body reference
Closes https://feedback.yaak.app/p/copy-body-only-works-on-first-click
2025-05-08 08:02:27 -07:00
Gregory Schier d3b8a42180 Update README.md 2025-05-07 12:00:34 -07:00
Gregory Schier 95f39c514a Update README.md 2025-05-07 11:59:01 -07:00
hexchain 7cba082eb0 Allow building and running on aarch64 Linux (#206)
Co-authored-by: Haochen Tong <haochentong@bytedance.com>
2025-05-01 08:06:13 -07:00
Billzabob 3b9b320be2 Send cookies for introspection (#204) 2025-04-30 10:13:06 -07:00
Gregory Schier 18664975a9 Padding on encrypted input 2025-04-26 07:33:32 -07:00
Gregory Schier bb014b7c43 Remove folder/environment foreign keys to make sync/import easier, and simplify batch upsert code. 2025-04-24 19:57:02 -07:00
Gregory Schier 9fa0650647 Add scrollbar to sidebar
Fixes: https://feedback.yaak.app/p/missing-scrollbar-on-request-list
2025-04-22 07:48:34 -07:00
Gregory Schier b8c42677ca Fix cmd+p filtering reference
https://feedback.yaak.app/p/search-doesnt-actually-search-through-all-the-apis
2025-04-22 07:46:10 -07:00
Gregory Schier 2eb3c2241c Fix duration tag
Closes: https://feedback.yaak.app/p/elapsed-time-not-stopping-on-failed-request
2025-04-22 07:29:17 -07:00
Gregory Schier 8fb7bbfe2e Don't prompt user for keychain password more than once 2025-04-22 07:23:05 -07:00
Gregory Schier 52eba74151 Handle no text 2025-04-22 07:01:48 -07:00
Gregory Schier e651760713 Merge remote-tracking branch 'origin/master' 2025-04-22 06:59:11 -07:00
Gregory Schier 82451a26f6 Use mimeType for response viewer 2025-04-22 06:58:53 -07:00
jzhangdev cc15f60fb6 Fix header layout (#182) 2025-04-22 06:51:39 -07:00
Gregory Schier 2f8b2a81c7 Fix jotai/index imports 2025-04-21 07:08:13 -07:00
Gregory Schier 6d4fdc91fe Fix text decoding when no content-type
Closes https://feedback.yaak.app/p/not-rendering-response
2025-04-21 06:54:03 -07:00
Gregory Schier faca29c789 Fix key/value re-render issue 2025-04-20 07:08:46 -07:00
Gregory Schier 1ab937aae4 Fix infinite GraphQL render loop 2025-04-17 14:45:33 -07:00
Gregory Schier 45fcea1954 Real-time response time
Closes https://feedback.yaak.app/p/real-time-display-of-request-execution-timer
2025-04-17 14:16:10 -07:00
Gregory Schier 73554078d1 Add elapsed after headers 2025-04-17 07:01:31 -07:00
Gregory Schier a42a88de7b Don't parse URI for HTTP requests anymore.
Fixes https://feedback.yaak.app/p/using-chinese-characters-in-request-parameters-can-result-in-errors
2025-04-17 06:48:39 -07:00
Gregory Schier 14a6079176 Fix URL grammar for path parameters 2025-04-17 06:30:48 -07:00
Gregory Schier 6c513616c0 Don't vendor keyring (libdbus) 2025-04-16 10:46:12 -07:00
Gregory Schier cdf5f1b7a5 Fix vite and top-level-await build error 2025-04-15 07:57:32 -07:00
Gregory Schier 6566857d54 Adjust keychain config for dev 2025-04-15 07:28:01 -07:00
Gregory Schier 2e55a1bd6d [WIP] Encryption for secure values (#183) 2025-04-15 07:18:26 -07:00
Gregory Schier e114a85c39 Render gRPC request for reflection.
Closes https://feedback.yaak.app/p/grpc-address-reflection-and-address-bar-issues
2025-03-31 12:26:07 -07:00
Gregory Schier 92be088e6c useClickOutside account for right click 2025-03-31 11:57:50 -07:00
Gregory Schier f1757ae427 Generalized frontend model store (#193) 2025-03-31 11:56:17 -07:00
Gregory Schier ce885c3551 port window ext from encryption PR 2025-03-26 11:46:55 -07:00
Gregory Schier 17657a4d04 plugin:yaak-models|upsert PoC 2025-03-26 09:54:42 -07:00
Gregory Schier b7f62b78b1 Clean up DB refactor access (#192) 2025-03-26 07:54:58 -07:00
Gregory Schier 006284b99c Fix scrollbars 2025-03-25 09:38:15 -07:00
Gregory Schier bac3968aac Revert scrollbar fix 2025-03-25 09:23:45 -07:00
Gregory Schier e5fa044eda Merge remote-tracking branch 'origin/master' 2025-03-25 09:23:33 -07:00
Gregory Schier 5969120140 Fix folder upsert 2025-03-25 09:23:26 -07:00
dependabot[bot] 8801936ad2 Bump vite from 6.0.6 to 6.0.12 (#191)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 09:13:56 -07:00
Gregory Schier 1d37d46130 Database access refactor (#190) 2025-03-25 08:35:10 -07:00
Gregory Schier 445c30f3a9 Fix iframe scrollbar 2025-03-23 06:57:36 -07:00
Gregory Schier 5fedea38c2 Fix lint error 2025-03-21 07:42:05 -07:00
Gregory Schier d86549f492 Always show GQL schema dropdown.
Fixes https://feedback.yaak.app/p/unable-to-disable-graphql-automatic-introspection
2025-03-21 07:28:08 -07:00
Gregory Schier 4c4eaba7d2 Queries now use AppHandle instead of Window (#189) 2025-03-20 09:43:14 -07:00
Gregory Schier cf8f8743bb Remove non-existing import 2025-03-20 06:23:36 -07:00
Andy Bao aa75636026 Fix labels in GRPC service/method selector dropdown (#188) 2025-03-20 06:07:31 -07:00
Gregory Schier 2c41b243b6 Some cleanup around window creation 2025-03-20 06:05:17 -07:00
Gregory Schier 6aea343d4f Shorten data directory description 2025-03-19 11:41:14 -07:00
Gregory Schier c300e8cbd5 Fix dropdown refresh after Git init 2025-03-19 11:35:20 -07:00
dependabot[bot] 6e25c26e9f Bump zip from 2.1.6 to 2.4.1 in /src-tauri (#185)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 09:09:02 -07:00
Gregory Schier dce1455be7 Inline to_fixed_hash fn 2025-03-19 08:52:02 -07:00
Gregory Schier 736025b12f Move editor search to top 2025-03-19 08:22:57 -07:00
Gregory Schier cb9e9a67a3 Try registering URL scheme 2025-03-19 07:58:12 -07:00
Gregory Schier 93c323458f Tweak Git history table 2025-03-19 06:59:54 -07:00
Gregory Schier 6f8c03d8c1 Fix git confid for commit 2025-03-19 06:59:43 -07:00
Gregory Schier afd4228fcf Don't style scrollbars on mac 2025-03-19 06:49:14 -07:00
Gregory Schier d478e5a12e Hotkey scrolling 2025-03-19 06:48:29 -07:00
Gregory Schier 0db9ebe67d Better Codemirror search match styles 2025-03-19 06:48:07 -07:00
Gregory Schier 80ea5e6b91 Fix autoscroller header scrolling 2025-03-19 06:37:02 -07:00
Gregory Schier cb773babe1 Nested template functions (#186) 2025-03-18 12:49:19 -07:00
Gregory Schier b9ed554aca Remove useTemplating prop (#184) 2025-03-18 05:34:38 -07:00
Gregory Schier f42f3d0e27 Support multi-line params and env vars 2025-03-17 09:29:37 -07:00
Gregory Schier 93ba5b6e5c Fix close bracket bug 2025-03-13 13:09:13 -07:00
Gregory Schier be11d5968e Fix notification not showing all 2025-03-12 06:41:53 -07:00
Gregory Schier 0828599e4f Don't switch to XML for HTML responses.
Fixes https://feedback.yaak.app/p/issue-with-rendering-html-responses-after-update
2025-03-08 08:34:41 -08:00
dependabot[bot] f47d22c395 Bump ring from 0.17.8 to 0.17.13 in /src-tauri (#181)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-08 08:11:38 -08:00
Gregory Schier edf65a62c2 Bump openapi-to-postmanv2 2025-03-08 08:06:27 -08:00
Gregory Schier 12233cb6f6 Build plugins 2025-03-08 08:06:18 -08:00
Hao Xiang cdce2ac53a fix ws connection state (#175)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2025-03-08 08:03:16 -08:00
Gregory Schier f4d0371060 Merge remote-tracking branch 'origin/master' 2025-03-06 07:15:07 -08:00
Gregory Schier 787a0433cb Support _PREFIXED variable names and fail when variable missing 2025-03-06 07:15:02 -08:00
Gregory Schier 2cf2c13175 Bump dep 2025-03-06 07:00:19 -08:00
Gregory Schier 493e844c01 Fix access token refreshing 2025-03-06 07:00:11 -08:00
Gregory Schier 60ea408e51 Add sponsor button
Some people want to contribute additional funds, so this makes it easier.
2025-03-06 06:46:02 -08:00
Gregory Schier 0db0cdfd6c Only font rendering fix for Linux 2025-03-06 06:29:03 -08:00
Gregory Schier 26371e5f6b Ignore whitespace during content type detection 2025-03-06 06:22:21 -08:00
Hermes Junior 6b7c144a11 Fix font aliasing on webkit. (#178) 2025-03-06 06:19:16 -08:00
Andy Bao 62f43ca24c Fix wrong protoc includes path (#179) 2025-03-06 06:18:06 -08:00
Gregory Schier fbf4d3c11e Make rendering return Result, and handle infinite recursion 2025-03-05 13:49:45 -08:00
Gregory Schier 7a1a0689b0 Add ability to deactivate license 2025-03-05 07:13:19 -08:00
Hao Xiang 9ead45d67a fix plugin manager listen addr (#177) 2025-03-02 05:51:19 -08:00
Gregory Schier eb8153f409 Better trial activation flows 2025-02-25 22:16:55 -08:00
Gregory Schier 80de232bec Fix dropdown button icon 2025-02-25 19:52:57 -08:00
Gregory Schier 7af8c95fea Allow opening workspace if sync dir not empty 2025-02-25 06:54:30 -08:00
Gregory Schier 2db72fe6ef Fix WS duplication from context menu 2025-02-25 06:10:35 -08:00
Gregory Schier d297e92a5a Fix content type parsing exception 2025-02-24 22:44:58 -08:00
Gregory Schier 7e1da4395d Build OAuth 2 plugin 2025-02-24 22:34:29 -08:00
Gregory Schier dfaeda224d Merge remote-tracking branch 'origin/main' 2025-02-24 22:34:15 -08:00
Gregory Schier c0dbe46318 Better data key for window 2025-02-24 22:34:10 -08:00
Gregory Schier 7f8b0479e1 Plugin window data directory key 2025-02-24 22:32:40 -08:00
Gregory Schier c8d6183456 Reduce plugin runtime memory 2025-02-24 12:20:47 -08:00
Gregory Schier 9d5f7784c4 Fix code splitting from tanstack/router migration 2025-02-24 07:12:45 -08:00
Gregory Schier 05ac836265 Remove analytics and add more update headers 2025-02-24 06:31:49 -08:00
Gregory Schier af7782c93b Better license flows 2025-02-24 05:59:15 -08:00
Gregory Schier 2b1431d041 Merge remote-tracking branch 'origin/master' 2025-02-23 06:25:59 -08:00
Gregory Schier 9d8b7a5265 Tweak getting content type 2025-02-23 06:25:53 -08:00
dependabot[bot] 95c12ad291 Bump openssl from 0.10.66 to 0.10.70 in /src-tauri (#161)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-23 06:09:26 -08:00
Gregory Schier dac2cec52f Merge remote-tracking branch 'origin/master' 2025-02-23 06:06:43 -08:00
Gregory Schier efe4eef1b7 Fix deleting selected environment 2025-02-23 06:05:01 -08:00
Hao Xiang a0e196a9e7 adding alternate key combinations for special shift (#173) 2025-02-22 07:00:04 -08:00
Gregory Schier abea5e6b5d Update README.md 2025-02-21 14:02:41 -08:00
Gregory Schier c6427dc724 Update README.md 2025-02-21 14:02:29 -08:00
Gregory Schier 8ce1e22b4e Update README.md 2025-02-21 13:57:51 -08:00
Gregory Schier 022d725e03 Update issue templates 2025-02-21 13:53:54 -08:00
Gregory Schier ed7fdb1b4c Only close brackets for json-like langs
Fixes #162
2025-02-21 13:50:21 -08:00
Gregory Schier 52937c3097 Soft required field 2025-02-21 13:43:19 -08:00
Gregory Schier 597b5bb783 Make all OAuth 2.0 fields optional
Closes mountain-loop/yaak#165
2025-02-21 13:40:05 -08:00
Gregory Schier e510204b8c More monospace fallbacks
Closes #167
2025-02-21 13:36:54 -08:00
Gregory Schier d31b4448df Pass templating vars to recursive children (closes #171) 2025-02-21 13:34:15 -08:00
Gregory Schier e420a0a45e Don't expand the directory setting when creating workspace 2025-02-21 13:18:47 -08:00
Gregory Schier 84ecbe0cd6 Better querystring import (https://feedback.yaak.app/p/url-pasted-params-parsed-incorrectly) 2025-02-21 13:16:09 -08:00
Gregory Schier 6a63cc26b9 Fix commit-and-push loading state 2025-02-19 10:35:41 -08:00
Gregory Schier 8ed0fd55c3 Remove environments from synced folder, and stop syncing 2025-02-19 10:35:31 -08:00
Gregory Schier 74f14a8392 Tweak some things for launch 2025-02-18 21:28:03 -08:00
Gregory Schier d9a1e124f5 package-lock.json 2025-01-31 09:05:19 -08:00
Gregory Schier 5f0b7055bf Bump plugin types 2025-01-31 09:05:09 -08:00
Gregory Schier 1ae6837842 Remove log 2025-01-27 08:37:54 -08:00
Gregory Schier 252d23bb0e Support for OAuth 2.0 (#5) 2025-01-26 13:32:17 -08:00
Gregory Schier d142966d0c Update plugins 2025-01-20 13:07:04 -08:00
Gregory Schier 26cce077bb Secret key to editor type 2025-01-17 15:21:41 -08:00
Gregory Schier 0491bed46d A few tweaks 2025-01-17 15:10:02 -08:00
Gregory Schier 16af8bf008 JWT plugin 2025-01-17 14:38:17 -08:00
Gregory Schier 064416398b JWT auth plugin and updates 2025-01-17 08:01:50 -08:00
Gregory Schier ebb7b69dd8 Add auth plugins 2025-01-16 15:28:25 -08:00
Gregory Schier e213c76870 More plugins (#4) 2025-01-14 10:52:32 -08:00
Gregory Schier a80a25a90e Update for standalone base environments 2025-01-13 17:04:35 -08:00
Gregory Schier f8b211be1c Support --url-query in curl import 2024-12-31 07:21:14 -08:00
Gregory Schier ab48f118af Handle no GraphQL variables 2024-10-22 08:05:58 -07:00
Gregory Schier 59b0b7321f Fix GraphQL variables 2024-10-22 07:41:28 -07:00
Gregory Schier d91e60f7e0 Support new GraphQL body type in curl export 2024-10-22 07:26:16 -07:00
Gregory Schier 9d24aefba1 Add template function descriptions 2024-10-15 07:47:26 -07:00
Gregory Schier 17a429525f Tweak plugins 2024-10-15 07:45:45 -07:00
Gregory Schier 61543fb10f Merge remote-tracking branch 'origin/main' 2024-10-08 14:49:37 -07:00
Gregory Schier 9291950554 Fix curl import when using boolean flags 2024-10-08 14:49:03 -07:00
Gregory Schier 54689d19ef Add more template tag plugins (#3) 2024-10-02 06:56:07 -07:00
Gregory Schier 9df586cb59 NPM workspaces 2024-09-30 18:11:51 -07:00
Gregory Schier 035d7927f9 Fix types 2024-09-22 11:09:18 -07:00
Gregory Schier 1a4e6de1f4 Merge remote-tracking branch 'origin/main' 2024-09-20 07:09:06 -07:00
Gregory Schier aed73482d1 Bump @yaakapp/api deps 2024-09-20 07:09:01 -07:00
Gregory Schier 31dbb15448 Update README.md 2024-09-19 14:01:29 -07:00
Gregory Schier 92ac91733e Handle Postman URL query and variable fields 2024-09-17 05:58:21 -07:00
Gregory Schier 29d2d0ec62 Prevent infinite recursion in response tag
Closes yaakapp/app#90
2024-09-16 06:37:48 -07:00
Gregory Schier 75df5f8094 setup-node v4 2024-09-09 12:17:24 -07:00
Gregory Schier 9ae932823f Oops, missed one 2024-09-09 12:13:52 -07:00
Gregory Schier 107fe46852 Add @yaakapp/cli dependency 2024-09-09 12:11:37 -07:00
Gregory Schier 63f391ea5f Build plugins in workflow 2024-09-09 12:08:33 -07:00
Gregory Schier 035441a492 Fix tests and lint 2024-09-09 11:49:05 -07:00
Gregory Schier 48e62eb1d9 CI workflow 2024-09-09 11:43:21 -07:00
Gregory Schier b72e037e6a Remove commented code 2024-09-09 11:37:44 -07:00
Gregory Schier de6ed1a0cc Undo CI job 2024-09-09 08:54:04 -07:00
Gregory Schier 41c0027391 Try something else 2024-09-09 08:53:37 -07:00
Gregory Schier 6ce1369a88 Upgrade deps 2024-09-06 06:39:57 -07:00
Gregory Schier af9c5c0294 Lowercase response function name 2024-08-26 15:02:44 -07:00
Gregory Schier d4baddc8d4 Fix bug 2024-08-26 13:10:22 -07:00
Gregory Schier 7ca3b9bd20 Send purpose with render request 2024-08-23 13:31:39 -07:00
Gregory Schier 5ba11ca788 Bump plugin deps 2024-08-22 11:27:57 -07:00
Gregory Schier d1871b19ee Template response plugin 2024-08-19 19:11:36 -07:00
Gregory Schier 54efb6ae4e Add @yaakapp/api everywhere 2024-08-15 06:17:33 -07:00
Gregory Schier bb3f948596 Don't add duplicate body headers for Postman 2024-07-23 12:41:01 -07:00
Gregory Schier afaf4e62d8 Better body handling in Postman 2024-07-23 08:26:00 -07:00
Gregory Schier 5db8f9117f Use cross-env 2024-07-22 18:44:33 -07:00
Gregory Schier 27e6668be5 Try fix import 2024-07-22 18:03:17 -07:00
Gregory Schier 6a24b31c6c Improved Postman and OpenAPI import 2024-07-22 18:00:13 -07:00
Gregory Schier 75a7cac783 Merge remote-tracking branch 'origin/main' 2024-07-22 14:42:01 -07:00
Gregory Schier 373bc75e98 OpenAPI import plugins 2024-07-22 14:04:37 -07:00
Gregory Schier 02fd8f22b2 Create README.md 2024-07-22 09:46:18 -07:00
Gregory Schier 4cbfe50fce Tweak 2024-07-21 16:04:29 -07:00
Gregory Schier 5ba18af021 Remove Yaak CLI npm package 2024-07-19 16:14:13 -07:00
Gregory Schier 324e7da282 Proper exit code 2024-07-19 15:40:46 -07:00
Gregory Schier 8efc38b3eb Vendor yaak-cli 2024-07-19 15:06:09 -07:00
Gregory Schier cc3cb6d14f Add npm ci to plugin builds 2024-07-19 14:41:59 -07:00
Gregory Schier 77825ee89e Update plugins 2024-07-19 14:39:10 -07:00
Gregory Schier 7625727324 package-lock 2024-07-19 11:10:35 -07:00
Gregory Schier 47b8c4dd6b A few tweaks 2024-07-18 16:37:20 -07:00
Gregory Schier a63b485b95 Move plugins to this repo 2024-06-27 21:44:45 -07:00
Gregory Schier d1d08963fb Initial commit 2024-06-27 21:35:39 -07:00
1037 changed files with 62783 additions and 233361 deletions
-6
View File
@@ -1,6 +0,0 @@
node_modules/
dist/
.eslintrc.cjs
.prettierrc.cjs
src-web/postcss.config.cjs
src-web/vite.config.ts
-49
View File
@@ -1,49 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:import/recommended',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
'eslint-config-prettier',
],
plugins: ['react-refresh'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.json'],
},
ignorePatterns: [
'scripts/**/*',
'packages/plugin-runtime/**/*',
'packages/plugin-runtime-types/**/*',
'src-tauri/**/*',
'src-web/tailwind.config.cjs',
'src-web/vite.config.ts',
],
settings: {
react: {
version: 'detect',
},
'import/resolver': {
node: {
paths: ['src-web'],
extensions: ['.ts', '.tsx'],
},
},
},
rules: {
'react-refresh/only-export-components': 'error',
'jsx-a11y/no-autofocus': 'off',
'react/react-in-jsx-scope': 'off',
'import/no-unresolved': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
disallowTypeAnnotations: true,
fixStyle: 'separate-type-imports',
},
],
},
};
+3
View File
@@ -1,2 +1,5 @@
src-tauri/vendored/**/* linguist-generated=true
src-tauri/gen/schemas/**/* linguist-generated=true
# Ensure consistent line endings for test files that check exact content
src-tauri/yaak-http/tests/test.txt text eol=lf
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: gschier
+38
View File
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
-18
View File
@@ -1,18 +0,0 @@
on:
pull_request:
branches: [develop]
name: CI (JS)
jobs:
test:
name: Lint/Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm test
-36
View File
@@ -1,36 +0,0 @@
on:
pull_request:
branches: [develop]
paths:
- src-tauri/**
- .github/workflows/**
name: CI (Rust)
defaults:
run:
working-directory: src-tauri
jobs:
test:
name: Check/Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev
- uses: dtolnay/rust-toolchain@stable
- uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- run: cargo check --all
- run: cargo test --all
+31
View File
@@ -0,0 +1,31 @@
on:
pull_request:
push:
branches:
- main
name: Lint and Test
permissions:
contents: read
jobs:
test:
name: Lint/Test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: 'src-tauri'
shared-key: ci
cache-on-failure: true
- run: npm ci
- run: npm run lint
- name: Run JS Tests
run: npm test
- name: Run Rust Tests
run: cargo test --all
working-directory: src-tauri
+57 -51
View File
@@ -3,9 +3,6 @@ on:
push:
tags: [ v* ]
env:
YAAK_PLUGINS_DIR: checkout/plugins
jobs:
build-artifacts:
permissions:
@@ -19,15 +16,34 @@ jobs:
- platform: 'macos-latest' # for Arm-based Macs (M1 and above).
args: '--target aarch64-apple-darwin'
yaak_arch: 'arm64'
os: 'macos'
targets: 'aarch64-apple-darwin'
- platform: 'macos-latest' # for Intel-based Macs.
args: '--target x86_64-apple-darwin'
yaak_arch: 'x64'
os: 'macos'
targets: 'x86_64-apple-darwin'
- platform: 'ubuntu-22.04'
args: ''
yaak_arch: 'x64'
os: 'ubuntu'
targets: ''
- platform: 'ubuntu-22.04-arm'
args: ''
yaak_arch: 'arm64'
os: 'ubuntu'
targets: ''
- platform: 'windows-latest'
args: ''
yaak_arch: 'x64'
os: 'windows'
targets: ''
# Windows ARM64
- platform: 'windows-latest'
args: '--target aarch64-pc-windows-msvc'
yaak_arch: 'arm64'
os: 'windows'
targets: 'aarch64-pc-windows-msvc'
runs-on: ${{ matrix.platform }}
timeout-minutes: 40
steps:
@@ -36,58 +52,49 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
targets: ${{ matrix.targets }}
- uses: actions/cache@v3
continue-on-error: false
- uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
src-tauri/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
workspaces: 'src-tauri'
shared-key: ci
cache-on-failure: true
- name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
- name: install dependencies (Linux only)
if: matrix.os == 'ubuntu'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: install dependencies (windows only)
if: matrix.platform == 'windows-latest'
run: cargo install --force trusted-signing-cli
- name: Install NPM Dependencies
run: |
npm ci
npm install @yaakapp/cli
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
- name: Install Protoc for plugin-runtime
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run JS build
run: npm run build
- name: Install trusted-signing-cli (Windows only)
if: matrix.os == 'windows'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$dir = "$env:USERPROFILE\trusted-signing"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$url = "https://github.com/Levminer/trusted-signing-cli/releases/download/0.8.0/trusted-signing-cli.exe"
$exe = Join-Path $dir "trusted-signing-cli.exe"
Invoke-WebRequest -Uri $url -OutFile $exe
echo $dir >> $env:GITHUB_PATH
& $exe --version
- name: Run lint
run: npm run lint
- name: Checkout yaakapp/plugins
uses: actions/checkout@v4
with:
repository: yaakapp/plugins
path: ${{ env.YAAK_PLUGINS_DIR }}
- run: npm ci
- run: npm run lint
- name: Run JS Tests
run: npm test
- name: Run Rust Tests
run: cargo test --all
working-directory: src-tauri
- name: Set version
run: npm run replace-version
@@ -96,7 +103,6 @@ jobs:
- uses: tauri-apps/tauri-action@v0
env:
YAAK_PLUGINS_DIR: ${{ env.YAAK_PLUGINS_DIR }}
YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
@@ -105,21 +111,21 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# Apple signing stuff
APPLE_CERTIFICATE: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ matrix.platform == 'macos-latest' && secrets.APPLE_TEAM_ID }}
APPLE_CERTIFICATE: ${{ matrix.os == 'macos' && secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ matrix.os == 'macos' && secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ matrix.os == 'macos' && secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ matrix.os == 'macos' && secrets.APPLE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ matrix.os == 'macos' && secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ matrix.os == 'macos' && secrets.APPLE_TEAM_ID }}
# Windows signing stuff
AZURE_CLIENT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ matrix.platform == 'windows-latest' && secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ matrix.os == 'windows' && secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ matrix.os == 'windows' && secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ matrix.os == 'windows' && secrets.AZURE_TENANT_ID }}
with:
tagName: 'v__VERSION__'
releaseName: 'Release __VERSION__'
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
prerelease: true
args: '${{ matrix.args }} --config ./src-tauri/tauri.release.conf.json'
+44
View File
@@ -0,0 +1,44 @@
name: Generate Sponsors README
on:
workflow_dispatch:
schedule:
- cron: 30 15 * * 0-6
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2
- name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ secrets.SPONSORS_PAT }}
file: 'README.md'
maximum: 1999
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="50px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false
include-private: true
marker: 'sponsors-base'
- name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ secrets.SPONSORS_PAT }}
file: 'README.md'
minimum: 2000
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="80px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false
include-private: true
marker: 'sponsors-premium'
# ⚠️ Note: You can use any deployment step here to automatically push the README
# changes back to your branch.
- name: Commit Changes
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: main
force: false
folder: '.'
+5
View File
@@ -15,6 +15,8 @@ dist-ssr
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
!.vscode/launch.json
.idea
.DS_Store
*.suo
@@ -23,6 +25,7 @@ dist-ssr
*.sln
*.sw?
.eslintcache
out
*.sqlite
*.sqlite-*
@@ -31,3 +34,5 @@ dist-ssr
.tmp
tmp
.zed
codebook.toml
-4
View File
@@ -1,4 +0,0 @@
node_modules/
dist/
out/
.prettierrc.cjs
-8
View File
@@ -1,8 +0,0 @@
export default {
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 100,
"bracketSpacing": true
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome", "rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"]
}
+26
View File
@@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Dev App",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start"]
},
{
"type": "node",
"request": "launch",
"name": "Build App",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start"]
},
{
"type": "node",
"request": "launch",
"name": "Bootstrap",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "bootstrap"]
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"biome.enabled": true,
"biome.lint.format.enable": true
}
+35 -9
View File
@@ -34,8 +34,6 @@ Run the `bootstrap` command to do some initial setup:
npm run bootstrap
```
_NOTE: Run with `YAAK_PLUGINS_DIR=<Path to yaakapp/plugins>` to re-build bundled plugins_
## Run the App
After bootstrapping, start the app in development mode:
@@ -44,19 +42,47 @@ After bootstrapping, start the app in development mode:
npm start
```
_NOTE: If working on bundled plugins, run with `YAAK_PLUGINS_DIR=<Path to yaakapp/plugins>`_
## SQLite Migrations
New migrations can be created from the `src-tauri/` directory:
```shell
cd src-tauri
sqlx migrate add migration-name
npm run migration
```
Run the app to apply the migrations.
Rerun the app to apply the migrations.
If nothing happens, try `cargo clean` and run the app again.
_Note: For safety, development builds use a separate database location from production builds._
_Note: Development builds use a separate database location from production builds._
## Lezer Grammar Generation
```sh
# Example
lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts
```
## Linting & Formatting
This repo uses Biome for linting and formatting (replacing ESLint + Prettier).
- Lint the entire repo:
```sh
npm run lint
```
- Auto-fix lint issues where possible:
```sh
npm run lint:fix
```
- Format code:
```sh
npm run format
```
Notes:
- Many workspace packages also expose the same scripts (`lint`, `lint:fix`, and `format`).
- TypeScript type-checking still runs separately via `tsc --noEmit` in relevant packages.
+58 -30
View File
@@ -1,42 +1,70 @@
# Yaak API Client
<p align="center">
<a href="https://github.com/JamesIves/github-sponsors-readme-action">
<img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/src-tauri/icons/icon.png">
</a>
</p>
Yaak is a desktop API client for interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC
APIs. It's built using [Tauri](https://tauri.app), Rust, and ReactJS.
<h1 align="center">
💫 Yaak ➟ Desktop API Client 💫
</h1>
![screenshot](https://github.com/user-attachments/assets/f18e963f-0b68-4ecb-b8b8-cb71aa9aec02)
<p align="center">
A fast, privacy-first API client for REST, GraphQL, SSE, WebSocket, and gRPC built with Tauri, Rust, and React.
</p>
<p align="center">
Development is funded by community-purchased <a href="https://yaak.app/pricing">licenses</a>. You can also <a href="https://github.com/sponsors/gschier">become a sponsor</a> to have your logo appear below. 💖
</p>
<br>
## Feature Overview
🪂 Import data from Postman, Insomnia, OpenAPI, Swagger, or Curl.<br/>
📤 Send requests via REST, GraphQL, Server Sent Events (SSE), WebSockets, or gRPC.<br/>
🔐 Automatically authorize requests with OAuth 2.0, JWT tokens, Basic Auth, and more.<br/>
🔎 Filter response bodies using JSONPath or XPath queries.<br/>
⛓️ Chain together multiple requests to dynamically reference values.<br/>
📂 Organize requests into workspaces and nested folders.<br/>
🧮 Use environment variables to easily switch between Prod and Dev.<br/>
🏷️ Send dynamic values like UUIDs or timestamps using template tags.<br/>
🎨 Choose from many of the included themes, or make your own.<br/>
💽 Mirror workspace data to a directory for integration with Git or Dropbox.<br/>
📜 View response history for each request.<br/>
🔌 Create your own plugins for authentication, template tags, and more!<br/>
🛜 Configure a proxy to access firewall-blocked APIs
## Feedback and Bug Reports
<p align="center">
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium -->
</p>
<p align="center">
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https:&#x2F;&#x2F;github.com&#x2F;seanwash.png" width="50px" alt="User avatar: seanwash" /></a>&nbsp;&nbsp;<a href="https://github.com/jerath"><img src="https:&#x2F;&#x2F;github.com&#x2F;jerath.png" width="50px" alt="User avatar: jerath" /></a>&nbsp;&nbsp;<a href="https://github.com/itsa-sh"><img src="https:&#x2F;&#x2F;github.com&#x2F;itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a>&nbsp;&nbsp;<a href="https://github.com/dmmulroy"><img src="https:&#x2F;&#x2F;github.com&#x2F;dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a>&nbsp;&nbsp;<a href="https://github.com/timcole"><img src="https:&#x2F;&#x2F;github.com&#x2F;timcole.png" width="50px" alt="User avatar: timcole" /></a>&nbsp;&nbsp;<a href="https://github.com/VLZH"><img src="https:&#x2F;&#x2F;github.com&#x2F;VLZH.png" width="50px" alt="User avatar: VLZH" /></a>&nbsp;&nbsp;<a href="https://github.com/terasaka2k"><img src="https:&#x2F;&#x2F;github.com&#x2F;terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a>&nbsp;&nbsp;<a href="https://github.com/andriyor"><img src="https:&#x2F;&#x2F;github.com&#x2F;andriyor.png" width="50px" alt="User avatar: andriyor" /></a>&nbsp;&nbsp;<a href="https://github.com/majudhu"><img src="https:&#x2F;&#x2F;github.com&#x2F;majudhu.png" width="50px" alt="User avatar: majudhu" /></a>&nbsp;&nbsp;<a href="https://github.com/axelrindle"><img src="https:&#x2F;&#x2F;github.com&#x2F;axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a>&nbsp;&nbsp;<a href="https://github.com/jirizverina"><img src="https:&#x2F;&#x2F;github.com&#x2F;jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="50px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/GRAYAH"><img src="https:&#x2F;&#x2F;github.com&#x2F;GRAYAH.png" width="50px" alt="User avatar: GRAYAH" /></a>&nbsp;&nbsp;<!-- sponsors-base -->
</p>
All feedback, bug reports, questions, and feature requests should be reported to
[feedback.yaak.app](https://feedback.yaak.app). Issues will be duplicated
in this repository if applicable.
![Yaak API Client](https://yaak.app/static/screenshot.png)
## Community Projects
- [`yaak2postman`](https://github.com/BiteCraft/yaak2postman) CLI for converting Yaak data
exports to Postman-compatible collections
## Features
Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it.
Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in.
### 🌐 Work with any API
- Import collections from Postman, Insomnia, OpenAPI, Swagger, or Curl.
- Send requests via REST, GraphQL, gRPC, WebSocket, or Server-Sent Events.
- Filter and inspect responses with JSONPath or XPath.
### 🔐 Stay secure
- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
- Secure sensitive values with encrypted secrets.
- Store secrets in your OS keychain.
### ☁️ Organize & collaborate
- Group requests into workspaces and nested folders.
- Use environment variables to switch between dev, staging, and prod.
- Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox.
### 🧩 Extend & customize
- Insert dynamic values like UUIDs or timestamps with template tags.
- Pick from built-in themes or build your own.
- Create plugins to extend authentication, template tags, or the UI.
## Contribution Policy
Yaak is open source, but only accepting contributions for bug fixes. See the
[`good first issue`](https://github.com/yaakapp/app/labels/good%20first%20issue) label for
issues that are more approachable for contribution.
Yaak is open source but only accepting contributions for bug fixes. To get started,
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
To get started, visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your
environment.
## Useful Resources
- [Feedback and Bug Reports](https://feedback.yaak.app)
- [Documentation](https://feedback.yaak.app/help)
- [Yaak vs Postman](https://yaak.app/alternatives/postman)
- [Yaak vs Bruno](https://yaak.app/alternatives/bruno)
- [Yaak vs Insomnia](https://yaak.app/alternatives/insomnia)
+51
View File
@@ -0,0 +1,51 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useKeyWithClickEvents": "off"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"bracketSpacing": true
},
"css": {
"parser": {
"tailwindDirectives": true
},
"linter": {
"enabled": false
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
},
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!**/build",
"!scripts",
"!packages/plugin-runtime",
"!packages/plugin-runtime-types",
"!src-tauri",
"!src-web/tailwind.config.cjs",
"!src-web/postcss.config.cjs",
"!src-web/vite.config.ts",
"!src-web/routeTree.gen.ts"
]
}
}
+9419 -6796
View File
File diff suppressed because it is too large Load Diff
+67 -21
View File
@@ -7,11 +7,49 @@
"url": "git+https://github.com/mountain-loop/yaak.git"
},
"workspaces": [
"packages/common-lib",
"packages/plugin-runtime",
"packages/plugin-runtime-types",
"packages/common-lib",
"src-tauri/yaak-license",
"plugins/action-copy-curl",
"plugins/action-copy-grpcurl",
"plugins/auth-apikey",
"plugins/auth-aws",
"plugins/auth-basic",
"plugins/auth-bearer",
"plugins/auth-jwt",
"plugins/auth-ntlm",
"plugins/auth-oauth2",
"plugins/auth-oauth1",
"plugins/filter-jsonpath",
"plugins/filter-xpath",
"plugins/importer-curl",
"plugins/importer-insomnia",
"plugins/importer-openapi",
"plugins/importer-postman",
"plugins/importer-postman-environment",
"plugins/importer-yaak",
"plugins/template-function-1password",
"plugins/template-function-cookie",
"plugins/template-function-ctx",
"plugins/template-function-encode",
"plugins/template-function-fs",
"plugins/template-function-hash",
"plugins/template-function-json",
"plugins/template-function-prompt",
"plugins/template-function-random",
"plugins/template-function-regex",
"plugins/template-function-timestamp",
"plugins/template-function-uuid",
"plugins/template-function-xml",
"plugins/template-function-request",
"plugins/template-function-response",
"plugins/themes-yaak",
"src-tauri",
"src-tauri/yaak-crypto",
"src-tauri/yaak-fonts",
"src-tauri/yaak-git",
"src-tauri/yaak-license",
"src-tauri/yaak-mac-window",
"src-tauri/yaak-models",
"src-tauri/yaak-plugins",
"src-tauri/yaak-sse",
@@ -23,31 +61,39 @@
"scripts": {
"start": "npm run app-dev",
"app-build": "tauri build",
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri.development.conf.json",
"migration": "node scripts/create-migration.cjs",
"build": "npm run --workspaces --if-present build",
"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",
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",
"lint": "npm run --workspaces --if-present lint",
"build-plugins": "npm run --workspaces --if-present build",
"test": "npm run --workspaces --if-present test",
"icons": "run-p icons:*",
"icons:dev": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
"icons:release": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
"bootstrap": "run-s bootstrap:*",
"bootstrap:install-wasm-pack": "node scripts/install-wasm-pack.cjs",
"bootstrap:build": "npm run build",
"bootstrap:vendor": "npm run vendor",
"vendor": "run-p vendor:*",
"vendor:vendor-plugins": "node scripts/vendor-plugins.cjs",
"vendor:vendor-protoc": "node scripts/vendor-protoc.cjs",
"vendor:vendor-node": "node scripts/vendor-node.cjs",
"lint": "run-p lint:*",
"lint:biome": "biome lint",
"lint:extra": "npm run --workspaces --if-present lint",
"format": "biome format --write .",
"replace-version": "node scripts/replace-version.cjs",
"tauri": "tauri",
"tauri-before-build": "npm run bootstrap && npm run --workspaces --if-present build",
"tauri-before-dev": "npm run --workspaces --if-present dev"
"tauri-before-build": "npm run bootstrap",
"tauri-before-dev": "workspaces-run --parallel -- npm run --workspaces --if-present dev"
},
"devDependencies": {
"@tauri-apps/cli": "^2.2.7",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^8",
"eslint-config-prettier": "^8",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.1.0",
"@biomejs/biome": "^2.3.7",
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.3.4",
"nodejs-file-downloader": "^4.13.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.4.2",
"typescript": "^5.7.2"
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"workspaces-run": "^1.0.2"
}
}
+4 -4
View File
@@ -1,12 +1,12 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// biome-ignore lint/suspicious/noExplicitAny: none
export function debounce(fn: (...args: any[]) => void, delay = 500) {
let timer: ReturnType<typeof setTimeout>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = function (...args: any[]) {
// biome-ignore lint/suspicious/noExplicitAny: none
const result = (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
result.cancel = function () {
result.cancel = () => {
clearTimeout(timer);
};
return result;
+16 -16
View File
@@ -1,20 +1,20 @@
export function formatSize(bytes: number): string {
let num;
let unit;
let num: number;
let unit: string;
if (bytes > 1000 * 1000 * 1000) {
num = bytes / 1000 / 1000 / 1000;
unit = 'GB';
} else if (bytes > 1000 * 1000) {
num = bytes / 1000 / 1000;
unit = 'MB';
} else if (bytes > 1000) {
num = bytes / 1000;
unit = 'KB';
} else {
num = bytes;
unit = 'B';
}
if (bytes > 1000 * 1000 * 1000) {
num = bytes / 1000 / 1000 / 1000;
unit = 'GB';
} else if (bytes > 1000 * 1000) {
num = bytes / 1000 / 1000;
unit = 'MB';
} else if (bytes > 1000) {
num = bytes / 1000;
unit = 'KB';
} else {
num = bytes;
unit = 'B';
}
return `${Math.round(num * 10) / 10} ${unit}`;
return `${Math.round(num * 10) / 10} ${unit}`;
}
+2
View File
@@ -1 +1,3 @@
export * from './debounce';
export * from './formatSize';
export * from './templateFunction';
+49
View File
@@ -0,0 +1,49 @@
import type {
CallTemplateFunctionArgs,
JsonPrimitive,
TemplateFunctionArg,
} from '@yaakapp-internal/plugins';
export function validateTemplateFunctionArgs(
fnName: string,
args: TemplateFunctionArg[],
values: CallTemplateFunctionArgs['values'],
): string | null {
for (const arg of args) {
if ('inputs' in arg && arg.inputs) {
// Recurse down
const err = validateTemplateFunctionArgs(fnName, arg.inputs, values);
if (err) return err;
}
if (!('name' in arg)) continue;
if (arg.optional) continue;
if (arg.defaultValue != null) continue;
if (arg.hidden) continue;
if (values[arg.name] != null) continue;
return `Missing required argument "${arg.label || arg.name}" for template function ${fnName}()`;
}
return null;
}
/** Recursively apply form input defaults to a set of values */
export function applyFormInputDefaults(
inputs: TemplateFunctionArg[],
values: { [p: string]: JsonPrimitive | undefined },
) {
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
for (const input of inputs) {
if ('defaultValue' in input && values[input.name] === undefined) {
newValues[input.name] = input.defaultValue;
}
if (input.type === 'checkbox' && values[input.name] === undefined) {
newValues[input.name] = false;
}
// Recurse down to all child inputs
if ('inputs' in input) {
newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
}
}
return newValues;
}
+28
View File
@@ -0,0 +1,28 @@
# Yaak Plugin API
Yaak is a desktop [API client](https://yaak.app/blog/yet-another-api-client) for
interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC APIs. It's
built using Tauri, Rust, and ReactJS.
Plugins can be created in TypeScript, which are executed alongside Yaak in a NodeJS
runtime. This package contains the TypeScript type definitions required to make building
Yaak plugins a breeze.
## Quick Start
The easiest way to get started is by generating a plugin with the Yaak CLI:
```shell
npx @yaakapp/cli generate
```
For more details on creating plugins, check out
the [Quick Start Guide](https://feedback.yaak.app/help/articles/6911763-plugins-quick-start)
## Installation
If you prefer starting from scratch, manually install the types package:
```shell
npm install -D @yaakapp/api
```
+17 -5
View File
@@ -1,6 +1,20 @@
{
"name": "@yaakapp/api",
"version": "0.4.1",
"version": "0.7.1",
"keywords": [
"api-client",
"insomnia-alternative",
"bruno-alternative",
"postman-alternative"
],
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak"
},
"bugs": {
"url": "https://feedback.yaak.app"
},
"homepage": "https://yaak.app",
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"files": [
@@ -17,11 +31,9 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
"@types/node": "^22.5.4"
"@types/node": "^24.0.13"
},
"devDependencies": {
"cpy-cli": "^5.0.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.6.2"
"cpy-cli": "^5.0.0"
}
}
@@ -0,0 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PluginVersion } from "./gen_search";
export type PluginNameVersion = { name: string, version: string, };
export type PluginSearchResponse = { plugins: Array<PluginVersion>, };
export type PluginUpdatesResponse = { plugins: Array<PluginNameVersion>, };
@@ -1,16 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Environment } from "./gen_models.js";
import type { Folder } from "./gen_models.js";
import type { GrpcRequest } from "./gen_models.js";
import type { HttpRequest } from "./gen_models.js";
import type { HttpResponse } from "./gen_models.js";
import type { JsonValue } from "./serde_json/JsonValue.js";
import type { WebsocketRequest } from "./gen_models.js";
import type { Workspace } from "./gen_models.js";
import type { Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace } from "./gen_models";
import type { JsonValue } from "./serde_json/JsonValue";
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, };
export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
@@ -23,17 +19,22 @@ export type CallHttpAuthenticationResponse = {
* HTTP headers to add to the request. Existing headers will be replaced, while
* new headers will be added.
*/
setHeaders: Array<HttpHeader>, };
setHeaders?: Array<HttpHeader>,
/**
* Query parameters to add to the request. Existing params will be replaced, while
* new params will be added.
*/
setQueryParameters?: Array<HttpHeader>, };
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonPrimitive }, };
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
export type CallTemplateFunctionResponse = { value: string | null, };
export type CallTemplateFunctionResponse = { value: string | null, error?: string, };
export type CloseWindowRequest = { label: string, };
@@ -67,13 +68,13 @@ extensions: Array<string>, };
export type FilterRequest = { content: string, filter: string, };
export type FilterResponse = { content: string, };
export type FilterResponse = { content: string, error?: string, };
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "h_stack" } & FormInputHStack | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
@@ -104,7 +105,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputCheckbox = {
/**
@@ -131,7 +136,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputEditor = {
/**
@@ -170,7 +179,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputFile = {
/**
@@ -205,7 +218,13 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputHStack = { inputs?: Array<FormInput>, hidden?: boolean, };
export type FormInputHttpRequest = {
/**
@@ -232,7 +251,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputMarkdown = { content: string, hidden?: boolean, };
@@ -265,7 +288,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputSelectOption = { label: string, value: string, };
@@ -306,18 +333,26 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, };
export type GetCookieValueResponse = { value: string | null, };
export type GetGrpcRequestActionsResponse = { actions: Array<GrpcRequestAction>, pluginRefId: string, };
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, };
export type GetHttpRequestActionsRequest = Record<string, never>;
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
export type GetHttpRequestByIdRequest = { id: string, };
@@ -328,7 +363,17 @@ export type GetKeyValueRequest = { key: string, };
export type GetKeyValueResponse = { value?: string, };
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
export type GetTemplateFunctionConfigRequest = { contextId: string, name: string, values: { [key in string]?: JsonPrimitive }, };
export type GetTemplateFunctionConfigResponse = { function: TemplateFunction, pluginRefId: string, };
export type GetTemplateFunctionSummaryResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
export type GetThemesRequest = Record<string, never>;
export type GetThemesResponse = { themes: Array<Theme>, };
export type GrpcRequestAction = { label: string, icon?: Icon, };
export type HttpAuthenticationAction = { label: string, icon?: Icon, };
@@ -344,23 +389,29 @@ export type ImportResources = { workspaces: Array<Workspace>, environments: Arra
export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: WindowContext, payload: InternalEventPayload, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, context: PluginContext, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "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": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "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": "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": "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": "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_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "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": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "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": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "window_info_request" } & WindowInfoRequest | { "type": "window_info_response" } & WindowInfoResponse | { "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;
export type ListCookieNamesRequest = {};
export type ListCookieNamesResponse = { names: Array<string>, };
export type OpenWindowRequest = { url: string,
/**
* Label for the window. If not provided, a random one will be generated.
*/
label: string, title?: string, size?: WindowSize, };
label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
export type PluginContext = { id: string, label: string | null, workspaceId: string | null, };
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/**
* Text to add to the confirmation button
*/
confirmText?: string,
confirmText?: string, password?: boolean,
/**
* Text to add to the cancel button
*/
@@ -372,6 +423,12 @@ 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, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
@@ -386,20 +443,59 @@ export type SetKeyValueRequest = { key: string, value: string, };
export type SetKeyValueResponse = {};
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
export type TemplateFunction = { name: string, description?: string,
export type TemplateFunction = { name: string, previewType?: TemplateFunctionPreviewType, description?: string,
/**
* Also support alternative names. This is useful for not breaking existing
* tags when changing the `name` property
*/
aliases?: Array<string>, args: Array<FormInput>, };
aliases?: Array<string>, args: Array<TemplateFunctionArg>,
/**
* A list of arg names to show in the inline preview. If not provided, none will be shown (for privacy reasons).
*/
previewArgs?: Array<string>, };
/**
* Similar to FormInput, but contains
*/
export type TemplateFunctionArg = FormInput;
export type TemplateFunctionPreviewType = "live" | "click" | "none";
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
export type TemplateRenderResponse = { data: JsonValue, };
export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
export type Theme = {
/**
* How the theme is identified. This should never be changed
*/
id: string,
/**
* The friendly name of the theme to be displayed to the user
*/
label: string,
/**
* Whether the theme will be used for dark or light appearance
*/
dark: boolean,
/**
* The default top-level colors for the theme
*/
base: ThemeComponentColors,
/**
* Optionally override theme for individual UI components for more control
*/
components?: ThemeComponents, };
export type ThemeComponentColors = { surface?: string, surfaceHighlight?: string, surfaceActive?: string, text?: string, textSubtle?: string, textSubtlest?: string, border?: string, borderSubtle?: string, borderFocus?: string, shadow?: string, backdrop?: string, selection?: string, primary?: string, secondary?: string, info?: string, success?: string, notice?: string, warning?: string, danger?: string, };
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
export type WindowInfoRequest = { label: string, };
export type WindowInfoResponse = { requestId: string | null, environmentId: string | null, workspaceId: string | null, label: string, };
export type WindowNavigateEvent = { url: string, };
@@ -1,14 +1,12 @@
// 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, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
@@ -24,4 +22,4 @@ export type HttpUrlParameter = { enabled?: boolean, name: string, value: string,
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type PluginMetadata = { version: string, name: string, displayName: string, description: string | null, homepageUrl: string | null, repositoryUrl: string | null, };
export type PluginVersion = { id: string, version: string, url: string, description: string | null, name: string, displayName: string, homepageUrl: string | null, repositoryUrl: string | null, checksum: string, readme: string | null, yanked: boolean, };
@@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type JsonValue = number | string | Array<JsonValue> | { [key in string]?: JsonValue };
export type JsonValue = number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null;
@@ -3,3 +3,7 @@ export type * from './themes';
export * from './bindings/gen_models';
export * from './bindings/gen_events';
// Some extras for utility
export type { PartialImportResources } from './plugins/ImporterPlugin';
@@ -3,22 +3,37 @@ import {
CallHttpAuthenticationRequest,
CallHttpAuthenticationResponse,
FormInput,
GetHttpAuthenticationConfigRequest,
GetHttpAuthenticationSummaryResponse,
HttpAuthenticationAction,
} from '../bindings/gen_events';
import { MaybePromise } from '../helpers';
import { Context } from './Context';
type DynamicFormInput = FormInput & {
dynamic(
type AddDynamicMethod<T> = {
dynamic?: (
ctx: Context,
args: GetHttpAuthenticationConfigRequest,
): MaybePromise<Partial<FormInput> | undefined | null>;
args: CallHttpAuthenticationActionArgs,
) => MaybePromise<Partial<T> | null | undefined>;
};
type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & {
inputs: Array<AddDynamic<FormInput>>;
dynamic?: (
ctx: Context,
args: CallHttpAuthenticationActionArgs,
) => MaybePromise<
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
>;
}
: T & AddDynamicMethod<T>
: never;
export type DynamicAuthenticationArg = AddDynamic<FormInput>;
export type AuthenticationPlugin = GetHttpAuthenticationSummaryResponse & {
args: (FormInput | DynamicFormInput)[];
args: DynamicAuthenticationArg[];
onApply(
ctx: Context,
args: CallHttpAuthenticationRequest,
@@ -1,19 +1,24 @@
import type {
FindHttpResponsesRequest,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse,
ListCookieNamesResponse,
OpenWindowRequest,
PromptTextRequest,
PromptTextResponse,
RenderGrpcRequestRequest,
RenderGrpcRequestResponse,
RenderHttpRequestRequest,
RenderHttpRequestResponse,
SendHttpRequestRequest,
SendHttpRequestResponse,
ShowToastRequest,
TemplateRenderRequest,
TemplateRenderResponse,
} from '../bindings/gen_events.ts';
import type { JsonValue } from '../bindings/serde_json/JsonValue';
export interface Context {
clipboard: {
@@ -31,10 +36,23 @@ export interface Context {
delete(key: string): Promise<boolean>;
};
window: {
requestId(): Promise<string | null>;
workspaceId(): Promise<string | null>;
environmentId(): Promise<string | null>;
openUrl(
args: OpenWindowRequest & { onNavigate?: (args: { url: string }) => void },
args: OpenWindowRequest & {
onNavigate?: (args: { url: string }) => void;
onClose?: () => void;
},
): Promise<{ close: () => void }>;
};
cookies: {
listNames(): Promise<ListCookieNamesResponse['names']>;
getValue(args: GetCookieValueRequest): Promise<GetCookieValueResponse['value']>;
};
grpcRequest: {
render(args: RenderGrpcRequestRequest): Promise<RenderGrpcRequestResponse['grpcRequest']>;
};
httpRequest: {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
@@ -44,6 +62,9 @@ export interface Context {
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
};
templates: {
render(args: TemplateRenderRequest): Promise<TemplateRenderResponse['data']>;
render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>;
};
plugin: {
reload(): void;
};
}
@@ -1,12 +1,11 @@
import { FilterResponse } from '../bindings/gen_events';
import type { Context } from './Context';
export type FilterPluginResponse = { filtered: string };
export type FilterPlugin = {
name: string;
description?: string;
onFilter(
ctx: Context,
args: { payload: string; filter: string; mimeType: string },
): Promise<FilterPluginResponse> | FilterPluginResponse;
): Promise<FilterResponse> | FilterResponse;
};
@@ -0,0 +1,6 @@
import { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
import type { Context } from './Context';
export type GrpcRequestActionPlugin = GrpcRequestAction & {
onSelect(ctx: Context, args: CallGrpcRequestActionArgs): Promise<void> | void;
};
@@ -1,19 +1,28 @@
import { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '../bindings/gen_models';
import type { AtLeast } from '../helpers';
import { ImportResources } from '../bindings/gen_events';
import { AtLeast, MaybePromise } from '../helpers';
import type { Context } from './Context';
type ImportPluginResponse = null | {
resources: {
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
httpRequests: AtLeast<HttpRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
grpcRequests: AtLeast<GrpcRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
};
type RootFields = 'name' | 'id' | 'model';
type CommonFields = RootFields | 'workspaceId';
export type PartialImportResources = {
workspaces: Array<AtLeast<ImportResources['workspaces'][0], RootFields>>;
environments: Array<AtLeast<ImportResources['environments'][0], CommonFields>>;
folders: Array<AtLeast<ImportResources['folders'][0], CommonFields>>;
httpRequests: Array<AtLeast<ImportResources['httpRequests'][0], CommonFields>>;
grpcRequests: Array<AtLeast<ImportResources['grpcRequests'][0], CommonFields>>;
websocketRequests: Array<AtLeast<ImportResources['websocketRequests'][0], CommonFields>>;
};
export type ImportPluginResponse = null | {
resources: PartialImportResources;
};
export type ImporterPlugin = {
name: string;
description?: string;
onImport(ctx: Context, args: { text: string }): Promise<ImportPluginResponse>;
onImport(
ctx: Context,
args: { text: string },
): MaybePromise<ImportPluginResponse | null | undefined>;
};
@@ -1,12 +1,31 @@
import {
CallTemplateFunctionArgs,
TemplateFunction,
} from "../bindings/gen_events";
import { Context } from "./Context";
import { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
import { MaybePromise } from '../helpers';
import { Context } from './Context';
export type TemplateFunctionPlugin = TemplateFunction & {
onRender(
type AddDynamicMethod<T> = {
dynamic?: (
ctx: Context,
args: CallTemplateFunctionArgs,
): Promise<string | null>;
) => MaybePromise<Partial<T> | null | undefined>;
};
type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & {
inputs: Array<AddDynamic<FormInput>>;
dynamic?: (
ctx: Context,
args: CallTemplateFunctionArgs,
) => MaybePromise<
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
>;
}
: T & AddDynamicMethod<T>
: never;
export type DynamicTemplateFunctionArg = AddDynamic<FormInput>;
export type TemplateFunctionPlugin = Omit<TemplateFunction, 'args'> & {
args: DynamicTemplateFunctionArg[];
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;
};
@@ -1,8 +1,3 @@
import { Index } from "../themes";
import { Context } from "./Context";
import { Theme } from '../bindings/gen_events';
export type ThemePlugin = {
name: string;
description?: string;
getTheme(ctx: Context, fileContents: string): Promise<Index>;
};
export type ThemePlugin = Theme;
@@ -1,20 +1,29 @@
import { AuthenticationPlugin } from './AuthenticationPlugin';
import type { Context } from './Context';
import type { FilterPlugin } from './FilterPlugin';
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
import type { ImporterPlugin } from './ImporterPlugin';
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import type { ThemePlugin } from './ThemePlugin';
export type { Context } from './Context';
export type { Context };
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
export type { DynamicAuthenticationArg } from './AuthenticationPlugin';
export type { TemplateFunctionPlugin };
/**
* The global structure of a Yaak plugin
*/
export type PluginDefinition = {
init?: (ctx: Context) => void | Promise<void>;
dispose?: () => void | Promise<void>;
importer?: ImporterPlugin;
theme?: ThemePlugin;
themes?: ThemePlugin[];
filter?: FilterPlugin;
authentication?: AuthenticationPlugin;
httpRequestActions?: HttpRequestActionPlugin[];
grpcRequestActions?: GrpcRequestActionPlugin[];
templateFunctions?: TemplateFunctionPlugin[];
};
+7 -2
View File
@@ -2,12 +2,17 @@
"compilerOptions": {
"module": "node16",
"target": "es6",
"lib": ["es2021"],
"lib": [
"es2021",
"dom"
],
"declaration": true,
"declarationDir": "./lib",
"outDir": "./lib",
"strict": true,
"types": ["node"]
"types": [
"node"
]
},
"files": [
"src/index.ts"
+1 -4
View File
@@ -3,10 +3,7 @@
"scripts": {
"bootstrap": "npm run build",
"build": "run-p build:*",
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=../../src-tauri/vendored/plugin-runtime/index.cjs",
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=../../src-tauri/vendored/plugin-runtime/index.worker.cjs",
"build:__main": "esbuild src/index.ts --bundle --platform=node --outfile=../../src-tauri/target/debug/vendored/plugin-runtime/index.cjs",
"build:__worker": "esbuild src/index.ts --bundle --platform=node --outfile=../../src-tauri/target/debug/vendored/plugin-runtime/index.worker.cjs"
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=../../src-tauri/vendored/plugin-runtime/index.cjs"
},
"dependencies": {
"ws": "^8.18.0"
+10 -5
View File
@@ -1,14 +1,19 @@
import type { InternalEvent } from "@yaakapp/api";
import EventEmitter from "node:events";
import type { InternalEvent } from '@yaakapp/api';
export class EventChannel {
emitter: EventEmitter = new EventEmitter();
#listeners = new Set<(event: InternalEvent) => void>();
emit(e: InternalEvent) {
this.emitter.emit("__plugin_event__", e);
for (const l of this.#listeners) {
l(e);
}
}
listen(cb: (e: InternalEvent) => void) {
this.emitter.on("__plugin_event__", cb);
this.#listeners.add(cb);
}
unlisten(cb: (e: InternalEvent) => void) {
this.#listeners.delete(cb);
}
}
+11 -41
View File
@@ -1,56 +1,26 @@
import { PluginContext } from '@yaakapp-internal/plugins';
import type { BootRequest, InternalEvent } from '@yaakapp/api';
import path from 'node:path';
import { Worker } from 'node:worker_threads';
import type { EventChannel } from './EventChannel';
import type { PluginWorkerData } from './index.worker';
import { PluginInstance, PluginWorkerData } from './PluginInstance';
export class PluginHandle {
#worker: Worker;
#instance: PluginInstance;
constructor(
readonly pluginRefId: string,
readonly bootRequest: BootRequest,
readonly events: EventChannel,
pluginRefId: string,
context: PluginContext,
bootRequest: BootRequest,
pluginToAppEvents: EventChannel,
) {
this.#worker = this.#createWorker();
const workerData: PluginWorkerData = { pluginRefId, context, bootRequest };
this.#instance = new PluginInstance(workerData, pluginToAppEvents);
}
sendToWorker(event: InternalEvent) {
this.#worker.postMessage(event);
this.#instance.postMessage(event);
}
async terminate() {
await this.#worker.terminate();
}
#createWorker(): Worker {
const workerPath = process.env.YAAK_WORKER_PATH ?? path.join(__dirname, 'index.worker.cjs');
const workerData: PluginWorkerData = {
pluginRefId: this.pluginRefId,
bootRequest: this.bootRequest,
};
const worker = new Worker(workerPath, {
workerData,
});
worker.on('message', (e) => this.events.emit(e));
worker.on('error', this.#handleError.bind(this));
worker.on('exit', this.#handleExit.bind(this));
console.log('Created plugin worker for ', this.bootRequest.dir);
return worker;
}
async #handleError(err: Error) {
console.error('Plugin errored', this.bootRequest.dir, err);
}
async #handleExit(code: number) {
if (code === 0) {
console.log('Plugin exited successfully', this.bootRequest.dir);
} else {
console.log('Plugin exited with status', code, this.bootRequest.dir);
}
await this.#instance.terminate();
}
}
@@ -0,0 +1,696 @@
import { applyFormInputDefaults, validateTemplateFunctionArgs } from '@yaakapp-internal/lib/templateFunction';
import {
BootRequest,
DeleteKeyValueResponse,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdResponse,
GetKeyValueResponse,
GrpcRequestAction,
HttpAuthenticationAction,
HttpRequestAction,
InternalEvent,
InternalEventPayload,
ListCookieNamesResponse,
PluginContext,
PromptTextResponse,
RenderGrpcRequestResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse,
TemplateFunction,
TemplateRenderResponse,
WindowInfoResponse,
} from '@yaakapp-internal/plugins';
import { Context, PluginDefinition } from '@yaakapp/api';
import console from 'node:console';
import { type Stats, statSync, watch } from 'node:fs';
import path from 'node:path';
import { applyDynamicFormInput } from './common';
import { EventChannel } from './EventChannel';
import { migrateTemplateFunctionSelectOptions } from './migrations';
export interface PluginWorkerData {
bootRequest: BootRequest;
pluginRefId: string;
context: PluginContext;
}
export class PluginInstance {
#workerData: PluginWorkerData;
#mod: PluginDefinition;
#pluginToAppEvents: EventChannel;
#appToPluginEvents: EventChannel;
constructor(workerData: PluginWorkerData, pluginEvents: EventChannel) {
this.#workerData = workerData;
this.#pluginToAppEvents = pluginEvents;
this.#appToPluginEvents = new EventChannel();
// Forward incoming events to onMessage()
this.#appToPluginEvents.listen(async (event) => {
await this.#onMessage(event);
});
this.#mod = {} as any;
const fileChangeCallback = async () => {
await this.#mod?.dispose?.();
this.#importModule();
await this.#mod?.init?.(this.#newCtx(workerData.context));
return this.#sendPayload(
workerData.context,
{
type: 'reload_response',
silent: false,
},
null,
);
};
if (this.#workerData.bootRequest.watch) {
watchFile(this.#pathMod(), fileChangeCallback);
watchFile(this.#pathPkg(), fileChangeCallback);
}
this.#importModule();
}
postMessage(event: InternalEvent) {
this.#appToPluginEvents.emit(event);
}
async terminate() {
await this.#mod?.dispose?.();
this.#unimportModule();
}
async #onMessage(event: InternalEvent) {
const ctx = this.#newCtx(event.context);
const { context, payload, id: replyId } = event;
try {
if (payload.type === 'boot_request') {
await this.#mod?.init?.(ctx);
this.#sendPayload(context, { type: 'boot_response' }, replyId);
return;
}
if (payload.type === 'terminate_request') {
const payload: InternalEventPayload = {
type: 'terminate_response',
};
await this.terminate();
this.#sendPayload(context, payload, replyId);
return;
}
if (
payload.type === 'import_request' &&
typeof this.#mod?.importer?.onImport === 'function'
) {
const reply = await this.#mod.importer.onImport(ctx, {
text: payload.content,
});
if (reply != null) {
const replyPayload: InternalEventPayload = {
type: 'import_response',
// deno-lint-ignore no-explicit-any
resources: reply.resources as any,
};
this.#sendPayload(context, replyPayload, replyId);
return;
} else {
// Send back an empty reply (below)
}
}
if (payload.type === 'filter_request' && typeof this.#mod?.filter?.onFilter === 'function') {
const reply = await this.#mod.filter.onFilter(ctx, {
filter: payload.filter,
payload: payload.content,
mimeType: payload.type,
});
this.#sendPayload(context, { type: 'filter_response', ...reply }, replyId);
return;
}
if (
payload.type === 'get_grpc_request_actions_request' &&
Array.isArray(this.#mod?.grpcRequestActions)
) {
const reply: GrpcRequestAction[] = this.#mod.grpcRequestActions.map((a) => ({
...a,
// Add everything except onSelect
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_grpc_request_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (
payload.type === 'get_http_request_actions_request' &&
Array.isArray(this.#mod?.httpRequestActions)
) {
const reply: HttpRequestAction[] = this.#mod.httpRequestActions.map((a) => ({
...a,
// Add everything except onSelect
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_http_request_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'get_themes_request' && Array.isArray(this.#mod?.themes)) {
const replyPayload: InternalEventPayload = {
type: 'get_themes_response',
themes: this.#mod.themes,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (
payload.type === 'get_template_function_summary_request' &&
Array.isArray(this.#mod?.templateFunctions)
) {
const functions: TemplateFunction[] = this.#mod.templateFunctions.map(
(templateFunction) => {
return {
...migrateTemplateFunctionSelectOptions(templateFunction),
// Add everything except render
onRender: undefined,
};
},
);
const replyPayload: InternalEventPayload = {
type: 'get_template_function_summary_response',
pluginRefId: this.#workerData.pluginRefId,
functions,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (
payload.type === 'get_template_function_config_request' &&
Array.isArray(this.#mod?.templateFunctions)
) {
let templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
if (templateFunction == null) {
this.#sendEmpty(context, replyId);
return;
}
const fn = {
...migrateTemplateFunctionSelectOptions(templateFunction),
onRender: undefined,
};
payload.values = applyFormInputDefaults(fn.args, payload.values);
const p = { ...payload, purpose: 'preview' } as const;
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, p);
const replyPayload: InternalEventPayload = {
type: 'get_template_function_config_response',
pluginRefId: this.#workerData.pluginRefId,
function: { ...fn, args: resolvedArgs },
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'get_http_authentication_summary_request' && this.#mod?.authentication) {
const replyPayload: InternalEventPayload = {
type: 'get_http_authentication_summary_response',
...this.#mod.authentication,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'get_http_authentication_config_request' && this.#mod?.authentication) {
const { args, actions } = this.#mod.authentication;
payload.values = applyFormInputDefaults(args, payload.values);
const resolvedArgs = await applyDynamicFormInput(ctx, args, payload);
const resolvedActions: HttpAuthenticationAction[] = [];
for (const { onSelect, ...action } of actions ?? []) {
resolvedActions.push(action);
}
const replyPayload: InternalEventPayload = {
type: 'get_http_authentication_config_response',
args: resolvedArgs,
actions: resolvedActions,
pluginRefId: this.#workerData.pluginRefId,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'call_http_authentication_request' && this.#mod?.authentication) {
const auth = this.#mod.authentication;
if (typeof auth?.onApply === 'function') {
auth.args = await applyDynamicFormInput(ctx, auth.args, payload);
payload.values = applyFormInputDefaults(auth.args, payload.values);
this.#sendPayload(
context,
{
type: 'call_http_authentication_response',
...(await auth.onApply(ctx, payload)),
},
replyId,
);
return;
}
}
if (
payload.type === 'call_http_authentication_action_request' &&
this.#mod.authentication != null
) {
const action = this.#mod.authentication.actions?.[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (
payload.type === 'call_http_request_action_request' &&
Array.isArray(this.#mod.httpRequestActions)
) {
const action = this.#mod.httpRequestActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (
payload.type === 'call_grpc_request_action_request' &&
Array.isArray(this.#mod.grpcRequestActions)
) {
const action = this.#mod.grpcRequestActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (
payload.type === 'call_template_function_request' &&
Array.isArray(this.#mod?.templateFunctions)
) {
const fn = this.#mod.templateFunctions.find((a) => a.name === payload.name);
if (
payload.args.purpose === 'preview' &&
(fn?.previewType === 'click' || fn?.previewType === 'none')
) {
// Send empty render response
this.#sendPayload(
context,
{
type: 'call_template_function_response',
value: null,
error: 'Live preview disabled for this function',
},
replyId,
);
} else if (typeof fn?.onRender === 'function') {
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, payload.args);
const values = applyFormInputDefaults(resolvedArgs, payload.args.values);
const error = validateTemplateFunctionArgs(fn.name, resolvedArgs, values);
if (error && payload.args.purpose !== 'preview') {
this.#sendPayload(
context,
{ type: 'call_template_function_response', value: null, error },
replyId,
);
return;
}
try {
const result = await fn.onRender(ctx, { ...payload.args, values });
this.#sendPayload(
context,
{ type: 'call_template_function_response', value: result ?? null },
replyId,
);
} catch (err) {
this.#sendPayload(
context,
{
type: 'call_template_function_response',
value: null,
error: `${err}`.replace(/^Error:\s*/g, ''),
},
replyId,
);
}
return;
}
}
} catch (err) {
const error = `${err}`.replace(/^Error:\s*/g, '');
console.log('Plugin call threw exception', payload.type, '→', error);
this.#sendPayload(context, { type: 'error_response', error }, replyId);
return;
}
// No matches, so send back an empty response so the caller doesn't block forever
this.#sendEmpty(context, replyId);
}
#pathMod() {
return path.posix.join(this.#workerData.bootRequest.dir, 'build', 'index.js');
}
#pathPkg() {
return path.join(this.#workerData.bootRequest.dir, 'package.json');
}
#unimportModule() {
const id = require.resolve(this.#pathMod());
delete require.cache[id];
}
#importModule() {
const id = require.resolve(this.#pathMod());
delete require.cache[id];
this.#mod = require(id).plugin;
}
#buildEventToSend(
context: PluginContext,
payload: InternalEventPayload,
replyId: string | null = null,
): InternalEvent {
return {
pluginRefId: this.#workerData.pluginRefId,
pluginName: path.basename(this.#workerData.bootRequest.dir),
id: genId(),
replyId,
payload,
context,
};
}
#sendPayload(
context: PluginContext,
payload: InternalEventPayload,
replyId: string | null,
): string {
const event = this.#buildEventToSend(context, payload, replyId);
this.#sendEvent(event);
return event.id;
}
#sendEvent(event: InternalEvent) {
// if (event.payload.type !== 'empty_response') {
// console.log('Sending event to app', this.#pkg.name, event.id, event.payload.type);
// }
this.#pluginToAppEvents.emit(event);
}
#sendEmpty(context: PluginContext, replyId: string | null = null): string {
return this.#sendPayload(context, { type: 'empty_response' }, replyId);
}
#sendForReply<T extends Omit<InternalEventPayload, 'type'>>(
context: PluginContext,
payload: InternalEventPayload,
): Promise<T> {
// 1. Build event to send
const eventToSend = this.#buildEventToSend(context, payload, null);
// 2. Spawn listener in background
const promise = new Promise<T>((resolve) => {
const cb = (event: InternalEvent) => {
if (event.replyId === eventToSend.id) {
this.#appToPluginEvents.unlisten(cb); // Unlisten, now that we're done
const { type: _, ...payload } = event.payload;
resolve(payload as T);
}
};
this.#appToPluginEvents.listen(cb);
});
// 3. Send the event after we start listening (to prevent race)
this.#sendEvent(eventToSend);
// 4. Return the listener promise
return promise as unknown as Promise<T>;
}
#sendAndListenForEvents(
context: PluginContext,
payload: InternalEventPayload,
onEvent: (event: InternalEventPayload) => void,
): void {
// 1. Build event to send
const eventToSend = this.#buildEventToSend(context, payload, null);
// 2. Listen for replies in the background
this.#appToPluginEvents.listen((event: InternalEvent) => {
if (event.replyId === eventToSend.id) {
onEvent(event.payload);
}
});
// 3. Send the event after we start listening (to prevent race)
this.#sendEvent(eventToSend);
}
#newCtx(context: PluginContext): Context {
const _windowInfo = async () => {
if (context.label == null) {
throw new Error("Can't get window context without an active window");
}
const payload: InternalEventPayload = {
type: 'window_info_request',
label: context.label,
};
return this.#sendForReply<WindowInfoResponse>(context, payload);
};
return {
clipboard: {
copyText: async (text) => {
await this.#sendForReply(context, {
type: 'copy_text_request',
text,
});
},
},
toast: {
show: async (args) => {
await this.#sendForReply(context, {
type: 'show_toast_request',
// Handle default here because null/undefined both convert to None in Rust translation
timeout: args.timeout === undefined ? 5000 : args.timeout,
...args,
});
},
},
window: {
requestId: async () => {
return (await _windowInfo()).requestId;
},
async workspaceId(): Promise<string | null> {
return (await _windowInfo()).workspaceId;
},
async environmentId(): Promise<string | null> {
return (await _windowInfo()).environmentId;
},
openUrl: async ({ onNavigate, onClose, ...args }) => {
args.label = args.label || `${Math.random()}`;
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
const onEvent = (event: InternalEventPayload) => {
if (event.type === 'window_navigate_event') {
onNavigate?.(event);
} else if (event.type === 'window_close_event') {
onClose?.();
}
};
this.#sendAndListenForEvents(context, payload, onEvent);
return {
close: () => {
const closePayload: InternalEventPayload = {
type: 'close_window_request',
label: args.label,
};
this.#sendPayload(context, closePayload, null);
},
};
},
},
prompt: {
text: async (args) => {
const reply: PromptTextResponse = await this.#sendForReply(context, {
type: 'prompt_text_request',
...args,
});
return reply.value;
},
},
httpResponse: {
find: async (args) => {
const payload = {
type: 'find_http_responses_request',
...args,
} as const;
const { httpResponses } = await this.#sendForReply<FindHttpResponsesResponse>(
context,
payload,
);
return httpResponses;
},
},
grpcRequest: {
render: async (args) => {
const payload = {
type: 'render_grpc_request_request',
...args,
} as const;
const { grpcRequest } = await this.#sendForReply<RenderGrpcRequestResponse>(
context,
payload,
);
return grpcRequest;
},
},
httpRequest: {
getById: async (args) => {
const payload = {
type: 'get_http_request_by_id_request',
...args,
} as const;
const { httpRequest } = await this.#sendForReply<GetHttpRequestByIdResponse>(
context,
payload,
);
return httpRequest;
},
send: async (args) => {
const payload = {
type: 'send_http_request_request',
...args,
} as const;
const { httpResponse } = await this.#sendForReply<SendHttpRequestResponse>(
context,
payload,
);
return httpResponse;
},
render: async (args) => {
const payload = {
type: 'render_http_request_request',
...args,
} as const;
const { httpRequest } = await this.#sendForReply<RenderHttpRequestResponse>(
context,
payload,
);
return httpRequest;
},
},
cookies: {
getValue: async (args: GetCookieValueRequest) => {
const payload = {
type: 'get_cookie_value_request',
...args,
} as const;
const { value } = await this.#sendForReply<GetCookieValueResponse>(context, payload);
return value;
},
listNames: async () => {
const payload = { type: 'list_cookie_names_request' } as const;
const { names } = await this.#sendForReply<ListCookieNamesResponse>(context, payload);
return names;
},
},
templates: {
/**
* Invoke Yaak's template engine to render a value. If the value is a nested type
* (eg. object), it will be recursively rendered.
*/
render: async (args) => {
const payload = { type: 'template_render_request', ...args } as const;
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
return result.data as any;
},
},
store: {
get: async <T>(key: string) => {
const payload = { type: 'get_key_value_request', key } as const;
const result = await this.#sendForReply<GetKeyValueResponse>(context, payload);
return result.value ? (JSON.parse(result.value) as T) : undefined;
},
set: async <T>(key: string, value: T) => {
const valueStr = JSON.stringify(value);
const payload: InternalEventPayload = {
type: 'set_key_value_request',
key,
value: valueStr,
};
await this.#sendForReply<GetKeyValueResponse>(context, payload);
},
delete: async (key: string) => {
const payload = { type: 'delete_key_value_request', key } as const;
const result = await this.#sendForReply<DeleteKeyValueResponse>(context, payload);
return result.deleted;
},
},
plugin: {
reload: () => {
this.#sendPayload(context, { type: 'reload_response', silent: true }, null);
},
},
};
}
}
function genId(len = 5): string {
const alphabet = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let id = '';
for (let i = 0; i < len; i++) {
id += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return id;
}
const watchedFiles: Record<string, Stats | null> = {};
/**
* Watch a file and trigger a callback on change.
*
* We also track the stat for each file because fs.watch() will
* trigger a "change" event when the access date changes.
*/
function watchFile(filepath: string, cb: () => void) {
watch(filepath, () => {
const stat = statSync(filepath, { throwIfNoEntry: false });
if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
watchedFiles[filepath] = stat ?? null;
cb();
}
});
}
+37
View File
@@ -0,0 +1,37 @@
import { CallHttpAuthenticationActionArgs, CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
export async function applyDynamicFormInput(
ctx: Context,
args: DynamicTemplateFunctionArg[],
callArgs: CallTemplateFunctionArgs,
): Promise<DynamicTemplateFunctionArg[]>;
export async function applyDynamicFormInput(
ctx: Context,
args: DynamicAuthenticationArg[],
callArgs: CallHttpAuthenticationActionArgs,
): Promise<DynamicAuthenticationArg[]>;
export async function applyDynamicFormInput(
ctx: Context,
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
const resolvedArgs: any[] = [];
for (const { dynamic, ...arg } of args) {
const newArg: any = {
...arg,
...(typeof dynamic === 'function' ? await dynamic(ctx, callArgs as any) : undefined),
};
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
try {
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
} catch (e) {
console.error('Failed to apply dynamic form input', e);
}
}
resolvedArgs.push(newArg);
}
return resolvedArgs;
}
+13 -4
View File
@@ -8,10 +8,15 @@ if (!port) {
throw new Error('Plugin runtime missing PORT')
}
const events = new EventChannel();
const host = process.env.HOST;
if (!host) {
throw new Error('Plugin runtime missing HOST')
}
const pluginToAppEvents = new EventChannel();
const plugins: Record<string, PluginHandle> = {};
const ws = new WebSocket(`ws://localhost:${port}`);
const ws = new WebSocket(`ws://${host}:${port}`);
ws.on('message', async (e: Buffer) => {
try {
@@ -25,7 +30,7 @@ ws.on('error', (err: any) => console.error('Plugin runtime websocket error', err
ws.on('close', (code: number) => console.log('Plugin runtime websocket closed', code));
// Listen for incoming events from plugins
events.listen((e) => {
pluginToAppEvents.listen((e) => {
const eventStr = JSON.stringify(e);
ws.send(eventStr);
});
@@ -34,7 +39,7 @@ async function handleIncoming(msg: string) {
const pluginEvent: InternalEvent = JSON.parse(msg);
// Handle special event to bootstrap plugin
if (pluginEvent.payload.type === 'boot_request') {
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, events);
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.context, pluginEvent.payload, pluginToAppEvents);
plugins[pluginEvent.pluginRefId] = plugin;
}
@@ -53,3 +58,7 @@ async function handleIncoming(msg: string) {
plugin.sendToWorker(pluginEvent);
}
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
-568
View File
@@ -1,568 +0,0 @@
// OAuth 2.0 spec -> https://datatracker.ietf.org/doc/html/rfc6749
import type {
BootRequest,
Context,
DeleteKeyValueResponse,
FindHttpResponsesResponse,
FormInput,
GetHttpRequestByIdResponse,
GetKeyValueResponse,
HttpAuthenticationAction,
HttpRequestAction,
InternalEvent,
InternalEventPayload,
JsonPrimitive,
PluginDefinition,
PromptTextResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse,
TemplateFunction,
TemplateRenderResponse,
WindowContext,
} from '@yaakapp/api';
import * as console from 'node:console';
import type { Stats } from 'node:fs';
import { readFileSync, statSync, watch } from 'node:fs';
import path from 'node:path';
import * as util from 'node:util';
import { parentPort as nullableParentPort, workerData } from 'node:worker_threads';
import { interceptStdout } from './interceptStdout';
import { migrateTemplateFunctionSelectOptions } from './migrations';
if (nullableParentPort == null) {
throw new Error('Worker does not have access to parentPort');
}
const parentPort = nullableParentPort;
export interface PluginWorkerData {
bootRequest: BootRequest;
pluginRefId: string;
}
function initialize(workerData: PluginWorkerData) {
const {
bootRequest: { dir: pluginDir, watch: enableWatch },
pluginRefId,
}: PluginWorkerData = workerData;
const pathPkg = path.join(pluginDir, 'package.json');
const pathMod = path.posix.join(pluginDir, 'build', 'index.js');
const pkg = JSON.parse(readFileSync(pathPkg, 'utf8'));
prefixStdout(`[plugin][${pkg.name}] %s`);
function buildEventToSend(
windowContext: WindowContext,
payload: InternalEventPayload,
replyId: string | null = null,
): InternalEvent {
return {
pluginRefId,
pluginName: path.basename(pluginDir),
id: genId(),
replyId,
payload,
windowContext,
};
}
function sendEmpty(windowContext: WindowContext, replyId: string | null = null): string {
return sendPayload(windowContext, { type: 'empty_response' }, replyId);
}
function sendPayload(
windowContext: WindowContext,
payload: InternalEventPayload,
replyId: string | null,
): string {
const event = buildEventToSend(windowContext, payload, replyId);
sendEvent(event);
return event.id;
}
function sendEvent(event: InternalEvent) {
if (event.payload.type !== 'empty_response') {
console.log('Sending event to app', event.id, event.payload.type);
}
parentPort.postMessage(event);
}
function sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
windowContext: WindowContext,
payload: InternalEventPayload,
): Promise<T> {
// 1. Build event to send
const eventToSend = buildEventToSend(windowContext, payload, null);
// 2. Spawn listener in background
const promise = new Promise<T>((resolve) => {
const cb = (event: InternalEvent) => {
if (event.replyId === eventToSend.id) {
parentPort.off('message', cb); // Unlisten, now that we're done
const { type: _, ...payload } = event.payload;
resolve(payload as T);
}
};
parentPort.on('message', cb);
});
// 3. Send the event after we start listening (to prevent race)
sendEvent(eventToSend);
// 4. Return the listener promise
return promise as unknown as Promise<T>;
}
function sendAndListenForEvents(
windowContext: WindowContext,
payload: InternalEventPayload,
onEvent: (event: InternalEventPayload) => void,
): void {
// 1. Build event to send
const eventToSend = buildEventToSend(windowContext, payload, null);
// 2. Listen for replies in the background
parentPort.on('message', (event: InternalEvent) => {
if (event.replyId === eventToSend.id) {
onEvent(event.payload);
}
});
// 3. Send the event after we start listening (to prevent race)
sendEvent(eventToSend);
}
// Reload plugin if the JS or package.json changes
const windowContextNone: WindowContext = { type: 'none' };
const fileChangeCallback = async () => {
importModule();
return sendPayload(windowContextNone, { type: 'reload_response' }, null);
};
if (enableWatch) {
watchFile(pathMod, fileChangeCallback);
watchFile(pathPkg, fileChangeCallback);
}
const newCtx = (event: InternalEvent): Context => ({
clipboard: {
async copyText(text) {
await sendAndWaitForReply(event.windowContext, {
type: 'copy_text_request',
text,
});
},
},
toast: {
async show(args) {
await sendAndWaitForReply(event.windowContext, {
type: 'show_toast_request',
...args,
});
},
},
window: {
async openUrl({ onNavigate, ...args }) {
args.label = args.label || `${Math.random()}`;
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
const onEvent = (event: InternalEventPayload) => {
if (event.type === 'window_navigate_event') {
onNavigate?.(event);
}
};
sendAndListenForEvents(event.windowContext, payload, onEvent);
return {
close: () => {
const closePayload: InternalEventPayload = {
type: 'close_window_request',
label: args.label,
};
sendPayload(event.windowContext, closePayload, null);
},
};
},
},
prompt: {
async text(args) {
const reply: PromptTextResponse = await sendAndWaitForReply(event.windowContext, {
type: 'prompt_text_request',
...args,
});
return reply.value;
},
},
httpResponse: {
async find(args) {
const payload = {
type: 'find_http_responses_request',
...args,
} as const;
const { httpResponses } = await sendAndWaitForReply<FindHttpResponsesResponse>(
event.windowContext,
payload,
);
return httpResponses;
},
},
httpRequest: {
async getById(args) {
const payload = {
type: 'get_http_request_by_id_request',
...args,
} as const;
const { httpRequest } = await sendAndWaitForReply<GetHttpRequestByIdResponse>(
event.windowContext,
payload,
);
return httpRequest;
},
async send(args) {
const payload = {
type: 'send_http_request_request',
...args,
} as const;
const { httpResponse } = await sendAndWaitForReply<SendHttpRequestResponse>(
event.windowContext,
payload,
);
return httpResponse;
},
async render(args) {
const payload = {
type: 'render_http_request_request',
...args,
} as const;
const { httpRequest } = await sendAndWaitForReply<RenderHttpRequestResponse>(
event.windowContext,
payload,
);
return httpRequest;
},
},
templates: {
/**
* Invoke Yaak's template engine to render a value. If the value is a nested type
* (eg. object), it will be recursively rendered.
*/
async render(args) {
const payload = { type: 'template_render_request', ...args } as const;
const result = await sendAndWaitForReply<TemplateRenderResponse>(
event.windowContext,
payload,
);
return result.data;
},
},
store: {
async get<T>(key: string) {
const payload = { type: 'get_key_value_request', key } as const;
const result = await sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
return result.value ? (JSON.parse(result.value) as T) : undefined;
},
async set<T>(key: string, value: T) {
const valueStr = JSON.stringify(value);
const payload: InternalEventPayload = {
type: 'set_key_value_request',
key,
value: valueStr,
};
await sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
},
async delete(key: string) {
const payload = { type: 'delete_key_value_request', key } as const;
const result = await sendAndWaitForReply<DeleteKeyValueResponse>(
event.windowContext,
payload,
);
return result.deleted;
},
},
});
let plug: PluginDefinition | null = null;
function importModule() {
const id = require.resolve(pathMod);
delete require.cache[id];
plug = require(id).plugin;
}
importModule();
// Message comes into the plugin to be processed
parentPort.on('message', async (event: InternalEvent) => {
const ctx = newCtx(event);
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: pkg.name,
version: pkg.version,
};
sendPayload(windowContext, payload, replyId);
return;
}
if (payload.type === 'terminate_request') {
const payload: InternalEventPayload = {
type: 'terminate_response',
};
sendPayload(windowContext, payload, replyId);
return;
}
if (payload.type === 'import_request' && typeof plug?.importer?.onImport === 'function') {
const reply = await plug.importer.onImport(ctx, {
text: payload.content,
});
if (reply != null) {
const replyPayload: InternalEventPayload = {
type: 'import_response',
// deno-lint-ignore no-explicit-any
resources: reply.resources as any,
};
sendPayload(windowContext, replyPayload, replyId);
return;
} else {
// Continue, to send back an empty reply
}
}
if (payload.type === 'filter_request' && typeof plug?.filter?.onFilter === 'function') {
const reply = await plug.filter.onFilter(ctx, {
filter: payload.filter,
payload: payload.content,
mimeType: payload.type,
});
const replyPayload: InternalEventPayload = {
type: 'filter_response',
content: reply.filtered,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (
payload.type === 'get_http_request_actions_request' &&
Array.isArray(plug?.httpRequestActions)
) {
const reply: HttpRequestAction[] = plug.httpRequestActions.map((a) => ({
...a,
// Add everything except onSelect
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_http_request_actions_response',
pluginRefId,
actions: reply,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (
payload.type === 'get_template_functions_request' &&
Array.isArray(plug?.templateFunctions)
) {
const reply: TemplateFunction[] = plug.templateFunctions.map((templateFunction) => {
return {
...migrateTemplateFunctionSelectOptions(templateFunction),
// Add everything except render
onRender: undefined,
};
});
const replyPayload: InternalEventPayload = {
type: 'get_template_functions_response',
pluginRefId,
functions: reply,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (payload.type === 'get_http_authentication_summary_request' && plug?.authentication) {
const { name, shortLabel, label } = plug.authentication;
const replyPayload: InternalEventPayload = {
type: 'get_http_authentication_summary_response',
name,
label,
shortLabel,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (payload.type === 'get_http_authentication_config_request' && plug?.authentication) {
const { args, actions } = plug.authentication;
const resolvedArgs: FormInput[] = [];
for (let i = 0; i < args.length; i++) {
let v = args[i];
if ('dynamic' in v) {
const dynamicAttrs = await v.dynamic(ctx, payload);
const { dynamic, ...other } = v;
resolvedArgs.push({ ...other, ...dynamicAttrs } as FormInput);
} else {
resolvedArgs.push(v);
}
}
const resolvedActions: HttpAuthenticationAction[] = [];
for (const { onSelect, ...action } of actions ?? []) {
resolvedActions.push(action);
}
const replyPayload: InternalEventPayload = {
type: 'get_http_authentication_config_response',
args: resolvedArgs,
actions: resolvedActions,
pluginRefId,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (payload.type === 'call_http_authentication_request' && plug?.authentication) {
const auth = plug.authentication;
if (typeof auth?.onApply === 'function') {
applyFormInputDefaults(auth.args, payload.values);
const result = await auth.onApply(ctx, payload);
sendPayload(
windowContext,
{
type: 'call_http_authentication_response',
setHeaders: result.setHeaders,
},
replyId,
);
return;
}
}
if (
payload.type === 'call_http_authentication_action_request' &&
plug?.authentication != null
) {
const action = plug.authentication.actions?.[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
sendEmpty(windowContext, replyId);
return;
}
}
if (
payload.type === 'call_http_request_action_request' &&
Array.isArray(plug?.httpRequestActions)
) {
const action = plug.httpRequestActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
sendEmpty(windowContext, replyId);
return;
}
}
if (
payload.type === 'call_template_function_request' &&
Array.isArray(plug?.templateFunctions)
) {
const action = plug.templateFunctions.find((a) => a.name === payload.name);
if (typeof action?.onRender === 'function') {
applyFormInputDefaults(action.args, payload.args.values);
const result = await action.onRender(ctx, payload.args);
sendPayload(
windowContext,
{
type: 'call_template_function_response',
value: result ?? null,
},
replyId,
);
return;
}
}
if (payload.type === 'reload_request') {
importModule();
}
} catch (err) {
console.log('Plugin call threw exception', payload.type, err);
sendPayload(
windowContext,
{
type: 'error_response',
error: `${err}`,
},
replyId,
);
return;
}
// No matches, so send back an empty response so the caller doesn't block forever
sendEmpty(windowContext, replyId);
});
}
initialize(workerData);
function genId(len = 5): string {
const alphabet = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let id = '';
for (let i = 0; i < len; i++) {
id += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return id;
}
function prefixStdout(s: string) {
if (!s.includes('%s')) {
throw new Error('Console prefix must contain a "%s" replacer');
}
interceptStdout((text: string) => {
const lines = text.split(/\n/);
let newText = '';
for (let i = 0; i < lines.length; i++) {
if (lines[i] == '') continue;
newText += util.format(s, lines[i]) + '\n';
}
return newText.trimEnd();
});
}
const watchedFiles: Record<string, Stats> = {};
/**
* Watch a file and trigger callback on change.
*
* We also track the stat for each file because fs.watch() will
* trigger a "change" event when the access date changes
*/
function watchFile(filepath: string, cb: (filepath: string) => void) {
watch(filepath, () => {
const stat = statSync(filepath);
if (stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
cb(filepath);
}
watchedFiles[filepath] = stat;
});
}
/** Recursively apply form input defaults to a set of values */
function applyFormInputDefaults(
inputs: FormInput[],
values: { [p: string]: JsonPrimitive | undefined },
) {
for (const input of inputs) {
if ('inputs' in input) {
applyFormInputDefaults(input.inputs ?? [], values);
} else if ('defaultValue' in input && values[input.name] === undefined) {
values[input.name] = input.defaultValue;
}
}
}
+5 -6
View File
@@ -1,6 +1,8 @@
import { TemplateFunction } from '@yaakapp/api';
import type { TemplateFunctionPlugin } from '@yaakapp/api';
export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): TemplateFunction {
export function migrateTemplateFunctionSelectOptions(
f: TemplateFunctionPlugin,
): TemplateFunctionPlugin {
const migratedArgs = f.args.map((a) => {
if (a.type === 'select') {
a.options = a.options.map((o) => ({
@@ -11,8 +13,5 @@ export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): Templ
return a;
});
return {
...f,
args: migratedArgs,
};
return { ...f, args: migratedArgs };
}
@@ -0,0 +1,150 @@
import { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
import { describe, expect, test } from 'vitest';
import { applyDynamicFormInput, applyFormInputDefaults } from '../src/common';
describe('applyFormInputDefaults', () => {
test('Works with top-level select', () => {
const args: DynamicTemplateFunctionArg[] = [
{
type: 'select',
name: 'test',
options: [{ label: 'Option 1', value: 'one' }],
defaultValue: 'one',
},
];
expect(applyFormInputDefaults(args, {})).toEqual({
test: 'one',
});
});
test('Works with existing value', () => {
const args: DynamicTemplateFunctionArg[] = [
{
type: 'select',
name: 'test',
options: [{ label: 'Option 1', value: 'one' }],
defaultValue: 'one',
},
];
expect(applyFormInputDefaults(args, { test: 'explicit' })).toEqual({
test: 'explicit',
});
});
test('Works with recursive select', () => {
const args: DynamicTemplateFunctionArg[] = [
{ type: 'text', name: 'dummy', defaultValue: 'top' },
{
type: 'accordion',
label: 'Test',
inputs: [
{ type: 'text', name: 'name', defaultValue: 'hello' },
{
type: 'select',
name: 'test',
options: [{ label: 'Option 1', value: 'one' }],
defaultValue: 'one',
},
],
},
];
expect(applyFormInputDefaults(args, {})).toEqual({
dummy: 'top',
test: 'one',
name: 'hello',
});
});
test('Works with dynamic options', () => {
const args: DynamicTemplateFunctionArg[] = [
{
type: 'select',
name: 'test',
defaultValue: 'one',
options: [],
dynamic() {
return { options: [{ label: 'Option 1', value: 'one' }] };
},
},
];
expect(applyFormInputDefaults(args, {})).toEqual({
test: 'one',
});
expect(applyFormInputDefaults(args, {})).toEqual({
test: 'one',
});
});
});
describe('applyDynamicFormInput', () => {
test('Works with plain input', async () => {
const ctx = {} as Context;
const args: DynamicTemplateFunctionArg[] = [
{ type: 'text', name: 'name' },
{ type: 'checkbox', name: 'checked' },
];
const callArgs: CallTemplateFunctionArgs = {
values: {},
purpose: 'preview',
};
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
{ type: 'text', name: 'name' },
{ type: 'checkbox', name: 'checked' },
]);
});
test('Works with dynamic input', async () => {
const ctx = {} as Context;
const args: DynamicTemplateFunctionArg[] = [
{
type: 'text',
name: 'name',
async dynamic(_ctx, _args) {
return { hidden: true };
},
},
];
const callArgs: CallTemplateFunctionArgs = {
values: {},
purpose: 'preview',
};
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
{ type: 'text', name: 'name', hidden: true },
]);
});
test('Works with recursive dynamic input', async () => {
const ctx = {} as Context;
const callArgs: CallTemplateFunctionArgs = {
values: { hello: 'world' },
purpose: 'preview',
};
const args: DynamicTemplateFunctionArg[] = [
{
type: 'banner',
inputs: [
{
type: 'text',
name: 'name',
async dynamic(_ctx, args) {
return { hidden: args.values.hello === 'world' };
},
},
],
},
];
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
{
type: 'banner',
inputs: [
{
type: 'text',
name: 'name',
hidden: true,
},
],
},
]);
});
});
+1
View File
@@ -0,0 +1 @@
*/build
+68
View File
@@ -0,0 +1,68 @@
# Copy as cUrl
A request action plugin for Yaak that converts HTTP requests into [curl](https://curl.se)
commands, making it easy to share, debug, and execute requests outside Yaak.
![Screenshot of context menu](screenshot.png)
## Overview
This plugin adds a 'Copy as Curl' action to HTTP requests, converting any request into its
equivalent curl command. This is useful for debugging, sharing requests with team members,
and executing requests in terminal environments where `curl` is available.
## How It Works
The plugin analyzes the given HTTP request and generates a properly formatted curl command
that includes:
- HTTP method (GET, POST, PUT, DELETE, etc.)
- Request URL with query parameters
- Headers (including authentication headers)
- Request body (for POST, PUT, PATCH requests)
- Authentication credentials
## Usage
1. Configure an HTTP request as usual in Yaak
2. Right-click on the request in the sidebar
3. Select 'Copy as Curl'
4. The command is copied to your clipboard
5. Share or execute the command
## Generated Curl Examples
### Simple GET Request
```bash
curl -X GET 'https://api.example.com/users' \
--header 'Accept: application/json'
```
### POST Request with JSON Data
```bash
curl -X POST 'https://api.example.com/users' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
"name": "John Doe",
"email": "john@example.com"
}'
```
### Request with Multi-part Form Data
```bash
curl -X POST 'yaak.app' \
--header 'Content-Type: multipart/form-data' \
--form 'hello=world' \
--form file=@/path/to/file.json
```
### Request with Authentication
```bash
curl -X GET 'https://api.example.com/protected' \
--user 'username:password'
```
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@yaak/action-copy-curl",
"displayName": "Copy as Curl",
"description": "Copy request as a curl command",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/action-copy-curl"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

+173
View File
@@ -0,0 +1,173 @@
import type { HttpRequest, PluginDefinition } from '@yaakapp/api';
const NEWLINE = '\\\n ';
export const plugin: PluginDefinition = {
httpRequestActions: [
{
label: 'Copy as Curl',
icon: 'copy',
async onSelect(ctx, args) {
const rendered_request = await ctx.httpRequest.render({
httpRequest: args.httpRequest,
purpose: 'preview',
});
const data = await convertToCurl(rendered_request);
await ctx.clipboard.copyText(data);
await ctx.toast.show({
message: 'Command copied to clipboard',
icon: 'copy',
color: 'success',
});
},
},
],
};
export async function convertToCurl(request: Partial<HttpRequest>) {
const xs = ['curl'];
// Add method and URL all on first line
if (request.method) xs.push('-X', request.method);
// Build final URL with parameters (compatible with old curl)
let finalUrl = request.url || '';
const urlParams = (request.urlParameters ?? []).filter(onlyEnabled);
if (urlParams.length > 0) {
// Build url
const [base, hash] = finalUrl.split('#');
const separator = base?.includes('?') ? '&' : '?';
const queryString = urlParams
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
.join('&');
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
}
// Add API key authentication
if (request.authenticationType === 'apikey') {
if (request.authentication?.location === 'query') {
const sep = finalUrl.includes('?') ? '&' : '?';
finalUrl = [
finalUrl,
sep,
encodeURIComponent(request.authentication?.key ?? 'token'),
'=',
encodeURIComponent(request.authentication?.value ?? ''),
].join('');
} else {
request.headers = request.headers ?? [];
request.headers.push({
name: request.authentication?.key ?? 'X-Api-Key',
value: request.authentication?.value ?? '',
});
}
}
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}`));
xs.push(NEWLINE);
}
// Add form params
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) {
let v = `${p.name}=@${p.file}`;
v += p.contentType ? `;type=${p.contentType}` : '';
xs.push(flag, v);
} else {
xs.push(flag, quote(`${p.name}=${p.value}`));
}
xs.push(NEWLINE);
}
} 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 (type !== 'none' && typeof request.body?.text === 'string') {
xs.push('--data', quote(request.body.text));
xs.push(NEWLINE);
}
// Add basic/digest authentication
if (request.authentication?.disabled !== true) {
if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {
if (request.authenticationType === 'digest') xs.push('--digest');
xs.push(
'--user',
quote(
`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`,
),
);
xs.push(NEWLINE);
}
// Add bearer authentication
if (request.authenticationType === 'bearer') {
const value =
`${request.authentication?.prefix ?? 'Bearer'} ${request.authentication?.token ?? ''}`.trim();
xs.push('--header', quote(`Authorization: ${value}`));
xs.push(NEWLINE);
}
if (request.authenticationType === 'auth-aws-sig-v4') {
xs.push(
'--aws-sigv4',
[
'aws',
'amz',
request.authentication?.region ?? '',
request.authentication?.service ?? '',
].join(':'),
);
xs.push(NEWLINE);
xs.push(
'--user',
quote(
`${request.authentication?.accessKeyId ?? ''}:${request.authentication?.secretAccessKey ?? ''}`,
),
);
if (request.authentication?.sessionToken) {
xs.push(NEWLINE);
xs.push('--header', quote(`X-Amz-Security-Token: ${request.authentication.sessionToken}`));
}
xs.push(NEWLINE);
}
}
// Remove trailing newline
if (xs[xs.length - 1] === NEWLINE) {
xs.splice(xs.length - 1, 1);
}
return xs.join(' ');
}
function quote(arg: string): string {
const escaped = arg.replace(/'/g, "\\'");
return `'${escaped}'`;
}
function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {
return v.enabled !== false && !!v.name;
}
function maybeParseJSON<T>(v: string, fallback: T) {
try {
return JSON.parse(v);
} catch {
return fallback;
}
}
@@ -0,0 +1,414 @@
import { describe, expect, test } from 'vitest';
import { convertToCurl } from '../src';
describe('exporter-curl', () => {
test('Exports GET with params', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
urlParameters: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
{ name: 'c', value: 'ccc', enabled: false },
],
}),
).toEqual([`curl 'https://yaak.app?a=aaa&b=bbb'`].join(' \\n '));
});
test('Exports GET with params and hash', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app/path#section',
urlParameters: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
{ name: 'c', value: 'ccc', enabled: false },
],
}),
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(' \\n '));
});
test('Exports POST with url form data', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/x-www-form-urlencoded',
body: {
form: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
{ name: 'c', value: 'ccc', enabled: false },
],
},
}),
).toEqual(
[`curl -X POST 'https://yaak.app'`, `--data 'a=aaa'`, `--data 'b=bbb'`].join(' \\\n '),
);
});
test('Exports POST with GraphQL data', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'graphql',
body: {
query: '{foo,bar}',
variables: '{"a": "aaa", "b": "bbb"}',
},
}),
).toEqual(
[
`curl -X POST 'https://yaak.app'`,
`--data '{"query":"{foo,bar}","variables":{"a":"aaa","b":"bbb"}}'`,
].join(' \\\n '),
);
});
test('Exports POST with GraphQL data no variables', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'graphql',
body: {
query: '{foo,bar}',
},
}),
).toEqual(
[`curl -X POST 'https://yaak.app'`, `--data '{"query":"{foo,bar}"}'`].join(' \\\n '),
);
});
test('Exports PUT with multipart form', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'PUT',
bodyType: 'multipart/form-data',
body: {
form: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
{ name: 'c', value: 'ccc', enabled: false },
{ name: 'f', file: '/foo/bar.png', contentType: 'image/png' },
],
},
}),
).toEqual(
[
`curl -X PUT 'https://yaak.app'`,
`--form 'a=aaa'`,
`--form 'b=bbb'`,
'--form f=@/foo/bar.png;type=image/png',
].join(' \\\n '),
);
});
test('Exports JSON body', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/json',
body: {
text: `{"foo":"bar's"}`,
},
headers: [{ name: 'Content-Type', value: 'application/json' }],
}),
).toEqual(
[
`curl -X POST 'https://yaak.app'`,
`--header 'Content-Type: application/json'`,
`--data '{"foo":"bar\\'s"}'`,
].join(' \\\n '),
);
});
test('Exports multi-line JSON body', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/json',
body: {
text: `{"foo":"bar",\n"baz":"qux"}`,
},
headers: [{ name: 'Content-Type', value: 'application/json' }],
}),
).toEqual(
[
`curl -X POST 'https://yaak.app'`,
`--header 'Content-Type: application/json'`,
`--data '{"foo":"bar",\n"baz":"qux"}'`,
].join(' \\\n '),
);
});
test('Exports headers', async () => {
expect(
await convertToCurl({
headers: [
{ name: 'a', value: 'aaa' },
{ name: 'b', value: 'bbb', enabled: true },
{ name: 'c', value: 'ccc', enabled: false },
],
}),
).toEqual([`curl ''`, `--header 'a: aaa'`, `--header 'b: bbb'`].join(' \\\n '));
});
test('Basic auth', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {
username: 'user',
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(' \\\n '));
});
test('Basic auth disabled', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {
disabled: true,
username: 'user',
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`].join(' \\\n '));
});
test('Broken basic auth', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {},
}),
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(' \\\n '));
});
test('Digest auth', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'digest',
authentication: {
username: 'user',
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--digest --user 'user:pass'`].join(' \\\n '));
});
test('Bearer auth', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
token: 'tok',
},
}),
).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({
url: 'https://yaak.app',
authenticationType: 'bearer',
authentication: {
username: 'user',
password: 'pass',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(' \\\n '));
});
test('AWS v4 auth', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'auth-aws-sig-v4',
authentication: {
accessKeyId: 'ak',
secretAccessKey: 'sk',
sessionToken: '',
region: 'us-east-1',
service: 's3',
},
}),
).toEqual(
[`curl 'https://yaak.app'`, '--aws-sigv4 aws:amz:us-east-1:s3', `--user 'ak:sk'`].join(
' \\\n ',
),
);
});
test('AWS v4 auth with session', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'auth-aws-sig-v4',
authentication: {
accessKeyId: 'ak',
secretAccessKey: 'sk',
sessionToken: 'st',
region: 'us-east-1',
service: 's3',
},
}),
).toEqual(
[
`curl 'https://yaak.app'`,
'--aws-sigv4 aws:amz:us-east-1:s3',
`--user 'ak:sk'`,
`--header 'X-Amz-Security-Token: st'`,
].join(' \\\n '),
);
});
test('API key auth header', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'apikey',
authentication: {
location: 'header',
key: 'X-Header',
value: 'my-token',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Header: my-token'`].join(' \\\n '));
});
test('API key auth header query', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app?hi=there',
urlParameters: [{ name: 'param', value: 'hi' }],
authenticationType: 'apikey',
authentication: {
location: 'query',
key: 'foo',
value: 'bar',
},
}),
).toEqual([`curl 'https://yaak.app?hi=there&param=hi&foo=bar'`].join(' \\\n '));
});
test('API key auth header query with params', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
urlParameters: [{ name: 'param', value: 'hi' }],
authenticationType: 'apikey',
authentication: {
location: 'query',
key: 'foo',
value: 'bar',
},
}),
).toEqual([`curl 'https://yaak.app?param=hi&foo=bar'`].join(' \\\n '));
});
test('API key auth header default', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'apikey',
authentication: {
location: 'header',
},
}),
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Api-Key: '`].join(' \\\n '));
});
test('API key auth query', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app',
authenticationType: 'apikey',
authentication: {
location: 'query',
key: 'foo',
value: 'bar-baz',
},
}),
).toEqual([`curl 'https://yaak.app?foo=bar-baz'`].join(' \\\n '));
});
test('API key auth query with existing', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app?foo=bar&baz=qux',
authenticationType: 'apikey',
authentication: {
location: 'query',
key: 'hi',
value: 'there',
},
}),
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&hi=there'`].join(' \\\n '));
});
test('API key auth query default', async () => {
expect(
await convertToCurl({
url: 'https://yaak.app?foo=bar&baz=qux',
authenticationType: 'apikey',
authentication: {
location: 'query',
},
}),
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&token='`].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 '));
});
});
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+76
View File
@@ -0,0 +1,76 @@
# Copy as gRPCurl
An HTTP request action plugin that converts gRPC requests
into [gRPCurl](https://github.com/fullstorydev/grpcurl) commands, enabling easy sharing,
debugging, and execution of gRPC calls outside Yaak.
![Screenshot of context menu](screenshot.png)
## Overview
This plugin adds a "Copy as gRPCurl" action to gRPC requests, converting any gRPC request
into its equivalent executable command. This is useful for debugging gRPC services,
sharing requests with team members, or executing gRPC calls in terminal environments where
`grpcurl` is available.
## How It Works
The plugin analyzes your gRPC request configuration and generates a properly formatted
`grpcurl` command that includes:
- gRPC service and method names
- Server address and port
- Request message data (JSON format)
- Metadata (headers)
- Authentication credentials
- Protocol buffer definitions
## Usage
1. Configure a gRPC request as usual in Yaak
2. Right-click on the request sidebar item
3. Select "Copy as gRPCurl" from the available actions
4. The command is copied to your clipboard
5. Share or execute the command
## Generated gRPCurl Examples
### Simple Unary Call
```bash
grpcurl -plaintext \
-d '{"name": "John Doe"}' \
localhost:9090 \
user.UserService/GetUser
```
### Call with Metadata
```bash
grpcurl -plaintext \
-H "authorization: Bearer my-token" \
-H "x-api-version: v1" \
-d '{"user_id": "12345"}' \
api.example.com:443 \
user.UserService/GetUserProfile
```
### Call with TLS
```bash
grpcurl \
-d '{"query": "search term"}' \
secure-api.example.com:443 \
search.SearchService/Search
```
### Call with Proto Files
```bash
grpcurl -import-path /path/to/protos \
-proto /other/path/to/user.proto \
-d '{"email": "user@example.com"}' \
localhost:9090 \
user.UserService/CreateUser
```
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@yaak/action-copy-grpcurl",
"displayName": "Copy as gRPCurl",
"description": "Copy gRPC request as a grpcurl command",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/action-copy-grpcurl"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

+155
View File
@@ -0,0 +1,155 @@
import path from 'node:path';
import type { GrpcRequest, PluginDefinition } from '@yaakapp/api';
const NEWLINE = '\\\n ';
export const plugin: PluginDefinition = {
grpcRequestActions: [
{
label: 'Copy as gRPCurl',
icon: 'copy',
async onSelect(ctx, args) {
const rendered_request = await ctx.grpcRequest.render({
grpcRequest: args.grpcRequest,
purpose: 'preview',
});
const data = await convert(rendered_request, args.protoFiles);
await ctx.clipboard.copyText(data);
await ctx.toast.show({
message: 'Command copied to clipboard',
icon: 'copy',
color: 'success',
});
},
},
],
};
export async function convert(request: Partial<GrpcRequest>, allProtoFiles: string[]) {
const xs = ['grpcurl'];
if (request.url?.startsWith('http://')) {
xs.push('-plaintext');
}
const protoIncludes = allProtoFiles.filter((f) => !f.endsWith('.proto'));
const protoFiles = allProtoFiles.filter((f) => f.endsWith('.proto'));
const inferredIncludes = new Set<string>();
for (const f of protoFiles) {
const protoDir = findParentProtoDir(f);
if (protoDir) {
inferredIncludes.add(protoDir);
} else {
inferredIncludes.add(path.posix.join(f, '..'));
inferredIncludes.add(path.posix.join(f, '..', '..'));
}
}
for (const f of protoIncludes) {
xs.push('-import-path', quote(f));
xs.push(NEWLINE);
}
for (const f of inferredIncludes.values()) {
xs.push('-import-path', quote(f));
xs.push(NEWLINE);
}
for (const f of protoFiles) {
xs.push('-proto', quote(f));
xs.push(NEWLINE);
}
// Add headers
for (const h of (request.metadata ?? []).filter(onlyEnabled)) {
xs.push('-H', quote(`${h.name}: ${h.value}`));
xs.push(NEWLINE);
}
// Add basic authentication
if (request.authentication?.disabled !== true) {
if (request.authenticationType === 'basic') {
const user = request.authentication?.username ?? '';
const pass = request.authentication?.password ?? '';
const encoded = btoa(`${user}:${pass}`);
xs.push('-H', quote(`Authorization: Basic ${encoded}`));
xs.push(NEWLINE);
} else if (request.authenticationType === 'bearer') {
// Add bearer authentication
xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
xs.push(NEWLINE);
} else if (request.authenticationType === 'apikey') {
if (request.authentication?.location === 'query') {
const sep = request.url?.includes('?') ? '&' : '?';
request.url = [
request.url,
sep,
encodeURIComponent(request.authentication?.key ?? 'token'),
'=',
encodeURIComponent(request.authentication?.value ?? ''),
].join('');
} else {
xs.push(
'-H',
quote(
`${request.authentication?.key ?? 'X-Api-Key'}: ${request.authentication?.value ?? ''}`,
),
);
}
xs.push(NEWLINE);
}
}
// Add form params
if (request.message) {
xs.push('-d', `${quote(JSON.stringify(JSON.parse(request.message)))}`);
xs.push(NEWLINE);
}
// Add the server address
if (request.url) {
const server = request.url.replace(/^https?:\/\//, ''); // remove protocol
xs.push(server);
xs.push(NEWLINE);
}
// Add service + method
if (request.service && request.method) {
xs.push(`${request.service}/${request.method}`);
xs.push(NEWLINE);
}
// Remove trailing newline
if (xs[xs.length - 1] === NEWLINE) {
xs.splice(xs.length - 1, 1);
}
return xs.join(' ');
}
function quote(arg: string): string {
const escaped = arg.replace(/'/g, "\\'");
return `'${escaped}'`;
}
function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {
return v.enabled !== false && !!v.name;
}
function findParentProtoDir(startPath: string): string | null {
let dir = path.resolve(startPath);
while (true) {
if (path.basename(dir) === 'proto') {
return dir;
}
const parent = path.dirname(dir);
if (parent === dir) {
return null; // Reached root
}
dir = parent;
}
}
@@ -0,0 +1,159 @@
import { describe, expect, test } from 'vitest';
import { convert } from '../src';
describe('exporter-curl', () => {
test('Simple example', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
},
[],
),
).toEqual(['grpcurl yaak.app'].join(' \\\n '));
});
test('Basic metadata', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
metadata: [
{ name: 'aaa', value: 'AAA' },
{ enabled: true, name: 'bbb', value: 'BBB' },
{ enabled: false, name: 'disabled', value: 'ddd' },
],
},
[],
),
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, 'yaak.app'].join(' \\\n '));
});
test('Basic auth', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'basic',
authentication: {
username: 'user',
password: 'pass',
},
},
[],
),
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, 'yaak.app'].join(' \\\n '));
});
test('API key auth', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'apikey',
authentication: {
key: 'X-Token',
value: 'tok',
},
},
[],
),
).toEqual([`grpcurl -H 'X-Token: tok'`, 'yaak.app'].join(' \\\n '));
});
test('API key auth', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
authenticationType: 'apikey',
authentication: {
location: 'query',
key: 'token',
value: 'tok 1',
},
},
[],
),
).toEqual(['grpcurl', 'yaak.app?token=tok%201'].join(' \\\n '));
});
test('Single proto file', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual(
[
`grpcurl -import-path '/foo/bar'`,
`-import-path '/foo'`,
`-proto '/foo/bar/baz.proto'`,
'yaak.app',
].join(' \\\n '),
);
});
test('Multiple proto files, same dir', async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/foo/bar/aaa.proto', '/foo/bar/bbb.proto']),
).toEqual(
[
`grpcurl -import-path '/foo/bar'`,
`-import-path '/foo'`,
`-proto '/foo/bar/aaa.proto'`,
`-proto '/foo/bar/bbb.proto'`,
'yaak.app',
].join(' \\\n '),
);
});
test('Multiple proto files, different dir', async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb/ccc.proto', '/xxx/yyy/zzz.proto']),
).toEqual(
[
`grpcurl -import-path '/aaa/bbb'`,
`-import-path '/aaa'`,
`-import-path '/xxx/yyy'`,
`-import-path '/xxx'`,
`-proto '/aaa/bbb/ccc.proto'`,
`-proto '/xxx/yyy/zzz.proto'`,
'yaak.app',
].join(' \\\n '),
);
});
test('Single include dir', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb'])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, 'yaak.app'].join(' \\\n '),
);
});
test('Multiple include dir', async () => {
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy'])).toEqual(
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, 'yaak.app'].join(' \\\n '),
);
});
test('Mixed proto and dirs', async () => {
expect(
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy', '/foo/bar.proto']),
).toEqual(
[
`grpcurl -import-path '/aaa/bbb'`,
`-import-path '/xxx/yyy'`,
`-import-path '/foo'`,
`-import-path '/'`,
`-proto '/foo/bar.proto'`,
'yaak.app',
].join(' \\\n '),
);
});
test('Sends data', async () => {
expect(
await convert(
{
url: 'https://yaak.app',
message: JSON.stringify({ foo: 'bar', baz: 1.0 }, null, 2),
},
['/foo.proto'],
),
).toEqual(
[
`grpcurl -import-path '/'`,
`-proto '/foo.proto'`,
`-d '{"foo":"bar","baz":1}'`,
'yaak.app',
].join(' \\\n '),
);
});
});
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@yaak/auth-apikey",
"displayName": "API Key Authentication",
"description": "Authenticate requests using an API key",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-apikey"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
}
}
+54
View File
@@ -0,0 +1,54 @@
import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
authentication: {
name: 'apikey',
label: 'API Key',
shortLabel: 'API Key',
args: [
{
type: 'select',
name: 'location',
label: 'Behavior',
defaultValue: 'header',
options: [
{ label: 'Insert Header', value: 'header' },
{ label: 'Append Query Parameter', value: 'query' },
],
},
{
type: 'text',
name: 'key',
label: 'Key',
dynamic: (_ctx, { values }) => {
return values.location === 'query'
? {
label: 'Parameter Name',
description: 'The name of the query parameter to add to the request',
}
: {
label: 'Header Name',
description: 'The name of the header to add to the request',
};
},
},
{
type: 'text',
name: 'value',
label: 'API Key',
optional: true,
password: true,
},
],
async onApply(_ctx, { values }) {
const key = String(values.key ?? '');
const value = String(values.value ?? '');
const location = String(values.location);
if (location === 'query') {
return { setQueryParameters: [{ name: key, value }] };
}
return { setHeaders: [{ name: key, value }] };
},
},
};
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+49
View File
@@ -0,0 +1,49 @@
# AWS Signature Version 4 Auth
A plugin for authenticating AWS-compatible requests using the
[AWS Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
This enables secure, signed requests to AWS services (or any S3-compatible APIs like
Cloudflare R2).
![Screenshot of AWS SigV4 UI](screenshot.png)
## Overview
This plugin provides AWS Signature authentication for API requests in Yaak. SigV4 is used
by nearly all AWS APIs to verify the authenticity and integrity of requests using
cryptographic signatures.
With this plugin, you can securely sign requests to AWS services such as S3, STS, Lambda,
API Gateway, DynamoDB, and more. You can also authenticate against S3-compatible services
like **Cloudflare R2**, **MinIO**, or **Wasabi**.
## How AWS Signature Version 4 Works
SigV4 signs requests by creating a hash of key request components (method, URL, headers,
and optionally the payload) using your AWS credentials. The resulting HMAC signature is
added in the `Authorization` header along with credential scope metadata.
Example header:
```
Authorization: AWS4-HMAC-SHA256 Credential=AKIA…/20251011/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abcdef123456…
```
Each request must include a timestamp (`X-Amz-Date`) and may include a session token if
using temporary credentials.
## Configuration
The plugin presents the following fields:
- **Access Key ID** Your AWS access key identifier
- **Secret Access Key** The secret associated with the access key
- **Session Token** *(optional)* Used for temporary or assumed-role credentials (treated as secret)
- **Region** AWS region (e.g., `us-east-1`)
- **Service** AWS service identifier (e.g., `sts`, `s3`, `execute-api`)
## Usage
1. Configure a request, folder, or workspace to use **AWS SigV4 Authentication**
2. Enter your AWS credentials and target service/region
3. The plugin will automatically sign outgoing requests with valid SigV4 headers
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@yaak/auth-aws",
"displayName": "AWS SigV4",
"description": "Authenticate requests using AWS SigV4 signing",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-aws"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
},
"dependencies": {
"aws4": "^1.13.2"
},
"devDependencies": {
"@types/aws4": "^1.11.6"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

+88
View File
@@ -0,0 +1,88 @@
import { URL } from 'node:url';
import type { PluginDefinition } from '@yaakapp/api';
import type { CallHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
import type { Request } from 'aws4';
import aws4 from 'aws4';
export const plugin: PluginDefinition = {
authentication: {
name: 'awsv4',
label: 'AWS Signature',
shortLabel: 'AWS v4',
args: [
{ name: 'accessKeyId', label: 'Access Key ID', type: 'text', password: true },
{
name: 'secretAccessKey',
label: 'Secret Access Key',
type: 'text',
password: true,
},
{
name: 'service',
label: 'Service Name',
type: 'text',
defaultValue: 'sts',
placeholder: 'sts',
description: 'The service that is receiving the request (sts, s3, sqs, ...)',
},
{
name: 'region',
label: 'Region',
type: 'text',
placeholder: 'us-east-1',
description: 'The region that is receiving the request (defaults to us-east-1)',
optional: true,
},
{
name: 'sessionToken',
label: 'Session Token',
type: 'text',
password: true,
optional: true,
description: 'Only required if you are using temporary credentials',
},
],
onApply(_ctx, { values, ...args }): CallHttpAuthenticationResponse {
const accessKeyId = String(values.accessKeyId || '');
const secretAccessKey = String(values.secretAccessKey || '');
const sessionToken = String(values.sessionToken || '') || undefined;
const url = new URL(args.url);
const headers: NonNullable<Request['headers']> = {};
for (const headerName of ['content-type', 'host', 'x-amz-date', 'x-amz-security-token']) {
const v = args.headers.find((h) => h.name.toLowerCase() === headerName);
if (v != null) {
headers[headerName] = v.value;
}
}
const signature = aws4.sign(
{
host: url.host,
method: args.method,
path: url.pathname + (url.search || ''),
service: String(values.service || 'sts'),
region: values.region ? String(values.region) : undefined,
headers,
doNotEncodePath: true,
},
{
accessKeyId,
secretAccessKey,
sessionToken,
},
);
if (signature.headers == null) {
return {};
}
return {
setHeaders: Object.entries(signature.headers)
.filter(([name]) => name !== 'content-type') // Don't add this because we already have it
.map(([name, value]) => ({ name, value: String(value || '') })),
};
},
},
};
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+44
View File
@@ -0,0 +1,44 @@
# Basic Authentication
A simple Basic Authentication plugin that implements HTTP Basic Auth according
to [RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617), enabling secure
authentication with username and password credentials.
![Screenshot of basic auth UI](screenshot.png)
## Overview
This plugin provides HTTP Basic Authentication support for API requests in Yaak. Basic
Auth is one of the most widely supported authentication methods, making it ideal for APIs
that require simple username/password authentication without the complexity of OAuth
flows.
## How Basic Authentication Works
Basic Authentication encodes your username and password credentials using Base64 encoding
and sends them in the `Authorization` header with each request. The format is:
```
Authorization: Basic <base64-encoded-credentials>
```
Where `<base64-encoded-credentials>` is the Base64 encoding of `username:password`.
## Configuration
The plugin presents two fields:
- **Username**: Username or user identifier
- **Password**: Password or authentication token
## Usage
1. Configure the request, folder, or workspace to use Basic Authentication
2. Enter your username and password in the authentication configuration
3. The plugin will automatically add the proper `Authorization` header to your requests
## Troubleshooting
- **401 Unauthorized**: Verify your username and password are correct
- **403 Forbidden**: Check if your account has the necessary permissions
- **Connection Issues**: Ensure you're using HTTPS for secure transmission
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@yaak/auth-basic",
"displayName": "Basic Authentication",
"description": "Authenticate requests using Basic Auth",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-basic"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

+29
View File
@@ -0,0 +1,29 @@
import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
authentication: {
name: 'basic',
label: 'Basic Auth',
shortLabel: 'Basic',
args: [
{
type: 'text',
name: 'username',
label: 'Username',
optional: true,
},
{
type: 'text',
name: 'password',
label: 'Password',
optional: true,
password: true,
},
],
async onApply(_ctx, { values }) {
const { username, password } = values;
const value = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
return { setHeaders: [{ name: 'Authorization', value }] };
},
},
};
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+47
View File
@@ -0,0 +1,47 @@
# Bearer Token Authentication Plugin
A Bearer Token authentication plugin for Yaak that
implements [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750), enabling secure API
access using tokens, API keys, and other bearer credentials.
![Screenshot of bearer auth UI](screenshot.png)
## Overview
This plugin provides Bearer Token authentication support for your API requests in Yaak.
Bearer Token authentication is widely used in modern APIs, especially those following REST
principles and OAuth 2.0 standards. It's the preferred method for APIs that issue access
tokens, API keys, or other bearer credentials.
## How Bearer Token Authentication Works
Bearer Token authentication sends your token in the `Authorization` header with each
request using the Bearer scheme:
```
Authorization: Bearer <your-token>
```
The token is transmitted as-is without any additional encoding, making it simple and
efficient for API authentication.
## Configuration
The plugin requires only one field:
- **Token**: Your bearer token, access token, API key, or other credential
- **Prefix**: The prefix to use for the Authorization header, which will be of the
format "<PREFIX> <TOKEN>"
## Usage
1. Configure the request, folder, or workspace to use Bearer Authentication
2. Enter the token and optional prefix in the authentication configuration
3. The plugin will automatically add the proper `Authorization` header to your requests
## Troubleshooting
- **401 Unauthorized**: Verify your token is valid and not expired
- **403 Forbidden**: Check if your token has the necessary permissions/scopes
- **Invalid Token Format**: Ensure you're using the complete token without truncation
- **Token Expiration**: Refresh or regenerate expired tokens
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@yaak/auth-bearer",
"displayName": "Bearer Authentication",
"description": "Authenticate requests using bearer authentication",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-bearer"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

+39
View File
@@ -0,0 +1,39 @@
import type { PluginDefinition } from '@yaakapp/api';
import type { CallHttpAuthenticationRequest } from '@yaakapp-internal/plugins';
export const plugin: PluginDefinition = {
authentication: {
name: 'bearer',
label: 'Bearer Token',
shortLabel: 'Bearer',
args: [
{
type: 'text',
name: 'token',
label: 'Token',
optional: true,
password: true,
},
{
type: 'text',
name: 'prefix',
label: 'Prefix',
optional: true,
placeholder: '',
defaultValue: 'Bearer',
description:
'The prefix to use for the Authorization header, which will be of the format "<PREFIX> <TOKEN>".',
},
],
async onApply(_ctx, { values }) {
return { setHeaders: [generateAuthorizationHeader(values)] };
},
},
};
function generateAuthorizationHeader(values: CallHttpAuthenticationRequest['values']) {
const token = String(values.token || '').trim();
const prefix = String(values.prefix || '').trim();
const value = `${prefix} ${token}`.trim();
return { name: 'Authorization', value };
}
+67
View File
@@ -0,0 +1,67 @@
import type { Context } from '@yaakapp/api';
import { describe, expect, test } from 'vitest';
import { plugin } from '../src';
const ctx = {} as Context;
describe('auth-bearer', () => {
test('No values', async () => {
expect(
await plugin.authentication?.onApply(ctx, {
values: {},
headers: [],
url: 'https://yaak.app',
method: 'POST',
contextId: '111',
}),
).toEqual({ setHeaders: [{ name: 'Authorization', value: '' }] });
});
test('Only token', async () => {
expect(
await plugin.authentication?.onApply(ctx, {
values: { token: 'my-token' },
headers: [],
url: 'https://yaak.app',
method: 'POST',
contextId: '111',
}),
).toEqual({ setHeaders: [{ name: 'Authorization', value: 'my-token' }] });
});
test('Only prefix', async () => {
expect(
await plugin.authentication?.onApply(ctx, {
values: { prefix: 'Hello' },
headers: [],
url: 'https://yaak.app',
method: 'POST',
contextId: '111',
}),
).toEqual({ setHeaders: [{ name: 'Authorization', value: 'Hello' }] });
});
test('Prefix and token', async () => {
expect(
await plugin.authentication?.onApply(ctx, {
values: { prefix: 'Hello', token: 'my-token' },
headers: [],
url: 'https://yaak.app',
method: 'POST',
contextId: '111',
}),
).toEqual({ setHeaders: [{ name: 'Authorization', value: 'Hello my-token' }] });
});
test('Extra spaces', async () => {
expect(
await plugin.authentication?.onApply(ctx, {
values: { prefix: '\t Hello ', token: ' \nmy-token ' },
headers: [],
url: 'https://yaak.app',
method: 'POST',
contextId: '111',
}),
).toEqual({ setHeaders: [{ name: 'Authorization', value: 'Hello my-token' }] });
});
});
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+53
View File
@@ -0,0 +1,53 @@
# JSON Web Token (JWT) Authentication
A [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT) authentication
plugin that supports token generation, signing, and automatic header management.
![Screenshot of JWT auth UI](screenshot.png)
## Overview
This plugin provides JWT authentication support for API requests. JWT is a compact,
URL-safe means of representing claims between two parties, commonly used for
authentication and information exchange in modern web applications and APIs.
## How JWT Authentication Works
JWT authentication involves creating a signed token containing claims about the user or
application. The token is sent in the `Authorization` header:
```
Authorization: Bearer <jwt-token>
```
A JWT consists of three parts separated by dots:
- **Header**: Contains the token type and signing algorithm
- **Payload**: Contains the claims (user data, permissions, expiration, etc.)
- **Signature**: Ensures the token hasn't been tampered with
## Usage
1. Configure the request, folder, or workspace to use JWT Authentication
2. Set up your signing algorithm and secret/key
3. Configure the required claims for your JWT
4. The plugin will generate, sign, and include the JWT in your requests
## Common Use Cases
JWT authentication is commonly used for:
- **Microservices Authentication**: Service-to-service communication
- **API Gateway Integration**: Authenticating with API gateways
- **Single Sign-On (SSO)**: Sharing authentication across applications
- **Stateless Authentication**: No server-side session storage required
- **Mobile App APIs**: Secure authentication for mobile applications
- **Third-party Integrations**: Authenticating with external services
## Troubleshooting
- **Invalid Signature**: Check your secret/key and algorithm configuration
- **Token Expired**: Verify expiration time settings
- **Invalid Claims**: Ensure required claims are properly configured
- **Algorithm Mismatch**: Verify the algorithm matches what the API expects
- **Key Format Issues**: Ensure RSA keys are in the correct PEM format
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@yaak/auth-jwt",
"displayName": "JSON Web Tokens",
"description": "Authenticate requests using JSON web tokens (JWT)",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-jwt"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
},
"dependencies": {
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

+119
View File
@@ -0,0 +1,119 @@
import type { PluginDefinition } from '@yaakapp/api';
import jwt from 'jsonwebtoken';
const algorithms = [
'HS256',
'HS384',
'HS512',
'RS256',
'RS384',
'RS512',
'PS256',
'PS384',
'PS512',
'ES256',
'ES384',
'ES512',
'none',
] as const;
const defaultAlgorithm = algorithms[0];
export const plugin: PluginDefinition = {
authentication: {
name: 'jwt',
label: 'JWT Bearer',
shortLabel: 'JWT',
args: [
{
type: 'select',
name: 'algorithm',
label: 'Algorithm',
hideLabel: true,
defaultValue: defaultAlgorithm,
options: algorithms.map((value) => ({ label: value === 'none' ? 'None' : value, value })),
},
{
type: 'text',
name: 'secret',
label: 'Secret or Private Key',
password: true,
optional: true,
multiLine: true,
},
{
type: 'checkbox',
name: 'secretBase64',
label: 'Secret is base64 encoded',
},
{
type: 'select',
name: 'location',
label: 'Behavior',
defaultValue: 'header',
options: [
{ label: 'Insert Header', value: 'header' },
{ label: 'Append Query Parameter', value: 'query' },
],
},
{
type: 'text',
name: 'name',
label: 'Header Name',
defaultValue: 'Authorization',
optional: true,
dynamic(_ctx, args) {
if (args.values.location === 'query') {
return {
label: 'Parameter Name',
description: 'The name of the query parameter to add to the request',
};
}
return {
label: 'Header Name',
description: 'The name of the header to add to the request',
};
},
},
{
type: 'text',
name: 'headerPrefix',
label: 'Header Prefix',
optional: true,
defaultValue: 'Bearer',
dynamic(_ctx, args) {
if (args.values.location === 'query') {
return {
hidden: true,
};
}
},
},
{
type: 'editor',
name: 'payload',
label: 'Payload',
language: 'json',
defaultValue: '{\n "foo": "bar"\n}',
placeholder: '{ }',
},
],
async onApply(_ctx, { values }) {
const { algorithm, secret: _secret, secretBase64, payload } = values;
const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`;
const token = jwt.sign(`${payload}`, secret, {
algorithm: algorithm as (typeof algorithms)[number],
});
if (values.location === 'query') {
const paramName = String(values.name || 'token');
const paramValue = String(values.value || '');
return { setQueryParameters: [{ name: paramName, value: paramValue }] };
}
const headerPrefix = values.headerPrefix != null ? values.headerPrefix : 'Bearer';
const headerName = String(values.name || 'Authorization');
const headerValue = `${headerPrefix} ${token}`.trim();
return { setHeaders: [{ name: headerName, value: headerValue }] };
},
},
};
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@yaak/auth-ntlm",
"displayName": "NTLM Authentication",
"description": "Authenticate requests using NTLM authentication",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-ntlm"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
},
"dependencies": {
"httpntlm": "^1.8.13"
}
}
+87
View File
@@ -0,0 +1,87 @@
import type { PluginDefinition } from '@yaakapp/api';
import { ntlm } from 'httpntlm';
export const plugin: PluginDefinition = {
authentication: {
name: 'windows',
label: 'NTLM Auth',
shortLabel: 'NTLM',
args: [
{
type: 'banner',
color: 'info',
inputs: [
{
type: 'markdown',
content:
'NTLM is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).',
},
],
},
{
type: 'text',
name: 'username',
label: 'Username',
optional: true,
},
{
type: 'text',
name: 'password',
label: 'Password',
optional: true,
password: true,
},
{
type: 'accordion',
label: 'Advanced',
inputs: [
{ name: 'domain', label: 'Domain', type: 'text', optional: true },
{ name: 'workstation', label: 'Workstation', type: 'text', optional: true },
],
},
],
async onApply(ctx, { values, method, url }) {
const username = values.username ? String(values.username) : undefined;
const password = values.password ? String(values.password) : undefined;
const domain = values.domain ? String(values.domain) : undefined;
const workstation = values.workstation ? String(values.workstation) : undefined;
const options = {
url,
username,
password,
workstation,
domain,
};
const type1 = ntlm.createType1Message(options);
const negotiateResponse = await ctx.httpRequest.send({
httpRequest: {
method,
url,
headers: [
{ name: 'Authorization', value: type1 },
{ name: 'Connection', value: 'keep-alive' },
],
},
});
const wwwAuthenticateHeader = negotiateResponse.headers.find(
(h) => h.name.toLowerCase() === 'www-authenticate',
);
if (!wwwAuthenticateHeader?.value) {
throw new Error('Unable to find www-authenticate response header for NTLM');
}
const type2 = ntlm.parseType2Message(wwwAuthenticateHeader.value, (err: Error | null) => {
if (err != null) throw err;
});
const type3 = ntlm.createType3Message(type2, options);
return { setHeaders: [{ name: 'Authorization', value: type3 }] };
},
},
};
+1
View File
@@ -0,0 +1 @@
declare module 'httpntlm';
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@yaak/auth-oauth1",
"displayName": "OAuth 1.0",
"description": "Authenticate requests using OAuth 1.0a",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-oauth1"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
},
"dependencies": {
"oauth-1.0a": "^2.2.6"
}
}
+210
View File
@@ -0,0 +1,210 @@
import crypto from 'node:crypto';
import type { Context, GetHttpAuthenticationConfigRequest, PluginDefinition } from '@yaakapp/api';
import OAuth from 'oauth-1.0a';
const signatures = {
HMAC_SHA1: 'HMAC-SHA1',
HMAC_SHA256: 'HMAC-SHA256',
HMAC_SHA512: 'HMAC-SHA512',
RSA_SHA1: 'RSA-SHA1',
RSA_SHA256: 'RSA-SHA256',
RSA_SHA512: 'RSA-SHA512',
PLAINTEXT: 'PLAINTEXT',
} as const;
const defaultSig = signatures.HMAC_SHA1;
const pkSigs = Object.values(signatures).filter((k) => k.startsWith('RSA-'));
const nonPkSigs = Object.values(signatures).filter((k) => !pkSigs.includes(k));
type SigMethod = (typeof signatures)[keyof typeof signatures];
function hiddenIfNot(
sigMethod: SigMethod[],
...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]
) {
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
const hasGrantType = sigMethod.find((t) => t === String(values.signatureMethod ?? defaultSig));
const hasOtherBools = other.every((t) => t(values));
const show = hasGrantType && hasOtherBools;
return { hidden: !show };
};
}
export const plugin: PluginDefinition = {
authentication: {
name: 'oauth1',
label: 'OAuth 1.0',
shortLabel: 'OAuth 1',
args: [
{
type: 'banner',
color: 'info',
inputs: [
{
type: 'markdown',
content:
'OAuth 1.0 is still in beta. Please submit any issues to [Feedback](https://yaak.app/feedback).',
},
],
},
{
name: 'signatureMethod',
label: 'Signature Method',
type: 'select',
defaultValue: defaultSig,
options: Object.values(signatures).map((v) => ({ label: v, value: v })),
},
{ name: 'consumerKey', label: 'Consumer Key', type: 'text', password: true, optional: true },
{
name: 'consumerSecret',
label: 'Consumer Secret',
type: 'text',
password: true,
optional: true,
},
{
name: 'tokenKey',
label: 'Access Token',
type: 'text',
password: true,
optional: true,
},
{
name: 'tokenSecret',
label: 'Token Secret',
type: 'text',
password: true,
optional: true,
dynamic: hiddenIfNot(nonPkSigs),
},
{
name: 'privateKey',
label: 'Private Key (RSA-SHA1)',
type: 'text',
multiLine: true,
optional: true,
password: true,
placeholder:
'-----BEGIN RSA PRIVATE KEY-----\nPrivate key in PEM format\n-----END RSA PRIVATE KEY-----',
dynamic: hiddenIfNot(pkSigs),
},
{
type: 'accordion',
label: 'Advanced',
inputs: [
{ name: 'callback', label: 'Callback Url', type: 'text', optional: true },
{ name: 'verifier', label: 'Verifier', type: 'text', optional: true, password: true },
{ name: 'timestamp', label: 'Timestamp', type: 'text', optional: true },
{ name: 'nonce', label: 'Nonce', type: 'text', optional: true },
{
name: 'version',
label: 'OAuth Version',
type: 'text',
optional: true,
defaultValue: '1.0',
},
{ name: 'realm', label: 'Realm', type: 'text', optional: true },
],
},
],
onApply(
_ctx,
{ values, method, url },
): {
setHeaders?: { name: string; value: string }[];
setQueryParameters?: { name: string; value: string }[];
} {
const consumerKey = String(values.consumerKey || '');
const consumerSecret = String(values.consumerSecret || '');
const signatureMethod = String(values.signatureMethod || signatures.HMAC_SHA1) as SigMethod;
const version = String(values.version || '1.0');
const realm = String(values.realm || '') || undefined;
const oauth = new OAuth({
consumer: { key: consumerKey, secret: consumerSecret },
signature_method: signatureMethod,
version,
hash_function: hashFunction(signatureMethod),
realm,
});
if (pkSigs.includes(signatureMethod)) {
oauth.getSigningKey = (tokenSecret?: string) => tokenSecret || '';
}
const requestUrl = new URL(url);
// Base request options passed to oauth-1.0a
const requestData: Omit<OAuth.RequestOptions, 'data'> & {
data: Record<string, string | string[]>;
} = {
method,
url: requestUrl.toString(),
includeBodyHash: false,
data: {},
};
// (1) Include existing query params in signature base string
for (const key of requestUrl.searchParams.keys()) {
if (key.startsWith('oauth_')) continue;
const all = requestUrl.searchParams.getAll(key);
const first = all[0];
if (first == null) continue;
requestData.data[key] = all.length > 1 ? all : first;
}
// (2) Manual oauth_* overrides
if (values.callback) requestData.data.oauth_callback = String(values.callback);
if (values.nonce) requestData.data.oauth_nonce = String(values.nonce);
if (values.timestamp) requestData.data.oauth_timestamp = String(values.timestamp);
if (values.verifier) requestData.data.oauth_verifier = String(values.verifier);
let token: OAuth.Token | { key: string } | undefined;
if (pkSigs.includes(signatureMethod)) {
token = {
key: String(values.tokenKey || ''),
secret: String(values.privateKey || ''),
};
} else if (values.tokenKey && values.tokenSecret) {
token = { key: String(values.tokenKey), secret: String(values.tokenSecret) };
} else if (values.tokenKey) {
token = { key: String(values.tokenKey) };
}
const authParams = oauth.authorize(requestData, token as OAuth.Token | undefined);
const { Authorization } = oauth.toHeader(authParams);
return { setHeaders: [{ name: 'Authorization', value: Authorization }] };
},
},
};
function hashFunction(signatureMethod: SigMethod) {
switch (signatureMethod) {
case signatures.HMAC_SHA1:
return (base: string, key: string) =>
crypto.createHmac('sha1', key).update(base).digest('base64');
case signatures.HMAC_SHA256:
return (base: string, key: string) =>
crypto.createHmac('sha256', key).update(base).digest('base64');
case signatures.HMAC_SHA512:
return (base: string, key: string) =>
crypto.createHmac('sha512', key).update(base).digest('base64');
case signatures.RSA_SHA1:
return (base: string, privateKey: string) =>
crypto.createSign('RSA-SHA1').update(base).sign(privateKey, 'base64');
case signatures.RSA_SHA256:
return (base: string, privateKey: string) =>
crypto.createSign('RSA-SHA256').update(base).sign(privateKey, 'base64');
case signatures.RSA_SHA512:
return (base: string, privateKey: string) =>
crypto.createSign('RSA-SHA512').update(base).sign(privateKey, 'base64');
case signatures.PLAINTEXT:
return (base: string) => base;
default:
return (base: string, key: string) =>
crypto.createHmac('sha1', key).update(base).digest('base64');
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
+72
View File
@@ -0,0 +1,72 @@
# OAuth 2.0 Authentication
An [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) authentication plugin that
supports multiple grant types and flows, enabling secure API authentication with OAuth 2.0
providers.
![Screenshot of OAuth 2.0 auth UI](screenshot.png)
## Overview
This plugin implements OAuth 2.0 authentication for requests, supporting the most common
OAuth 2.0 grant types used in modern API integrations. It handles token management,
automatic refresh, and [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key
for Code Exchange) for enhanced security.
## Supported Grant Types
### Authorization Code Flow
The most secure and commonly used OAuth 2.0 flow for web applications.
- Standard Authorization Code flow
- Optional PKCE (Proof Key for Code Exchange) for enhanced security
- Supports automatic token refresh
### Client Credentials Flow
Ideal for server-to-server authentication where no user interaction is required.
### Implicit Flow
Legacy flow for single-page applications (deprecated but still supported):
- Direct access token retrieval
- No refresh token support
- Suitable for legacy integrations
### Resource Owner Password Credentials Flow
Direct username/password authentication.
- User credentials are exchanged directly for tokens
- Should only be used with trusted applications
- Supports automatic token refresh
## Features
- **Automatic Token Management**: Handles token storage, expiration, and refresh
automatically
- **PKCE Support**: Enhanced security for Authorization Code flow
- **Token Persistence**: Stores tokens between sessions
- **Flexible Configuration**: Supports custom authorization and token endpoints
- **Scope Management**: Configure required OAuth scopes for your API
- **Error Handling**: Comprehensive error handling and user feedback
## Usage
1. Configure the request, folder, or workspace to use OAuth 2.0 Authentication
2. Select the appropriate grant type for your use case
3. Fill in the required OAuth 2.0 parameters from your API provider
4. The plugin will handle the authentication flow and token management automatically
## Compatibility
This plugin is compatible with OAuth 2.0 providers including:
- Google APIs
- Microsoft Graph
- GitHub API
- Auth0
- Okta
- And many other OAuth 2.0 compliant services
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@yaak/auth-oauth2",
"displayName": "OAuth 2.0",
"description": "Authenticate requests using OAuth 2.0",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/auth-oauth2"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

@@ -0,0 +1,77 @@
import { readFileSync } from 'node:fs';
import type { Context, HttpRequest, HttpUrlParameter } from '@yaakapp/api';
import type { AccessTokenRawResponse } from './store';
export async function fetchAccessToken(
ctx: Context,
{
accessTokenUrl,
scope,
audience,
params,
grantType,
credentialsInBody,
clientId,
clientSecret,
}: {
clientId: string;
clientSecret: string;
grantType: string;
accessTokenUrl: string;
scope: string | null;
audience: string | null;
credentialsInBody: boolean;
params: HttpUrlParameter[];
},
): Promise<AccessTokenRawResponse> {
console.log('[oauth2] Getting access token', accessTokenUrl);
const httpRequest: Partial<HttpRequest> = {
method: 'POST',
url: accessTokenUrl,
bodyType: 'application/x-www-form-urlencoded',
body: {
form: [{ name: 'grant_type', value: grantType }, ...params],
},
headers: [
{ name: 'User-Agent', value: 'yaak' },
{ name: 'Accept', value: 'application/x-www-form-urlencoded, application/json' },
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' },
],
};
if (scope) httpRequest.body?.form.push({ name: 'scope', value: scope });
if (audience) httpRequest.body?.form.push({ name: 'audience', value: audience });
if (credentialsInBody) {
httpRequest.body?.form.push({ name: 'client_id', value: clientId });
httpRequest.body?.form.push({ name: 'client_secret', value: clientSecret });
} else {
const value = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
httpRequest.headers?.push({ name: 'Authorization', value });
}
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
const resp = await ctx.httpRequest.send({ httpRequest });
console.log('[oauth2] Got access token response', resp.status);
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
if (resp.status < 200 || resp.status >= 300) {
throw new Error(`Failed to fetch access token with status=${resp.status} and body=${body}`);
}
// biome-ignore lint/suspicious/noExplicitAny: none
let response: any;
try {
response = JSON.parse(body);
} catch {
response = Object.fromEntries(new URLSearchParams(body));
}
if (response.error) {
throw new Error(`Failed to fetch access token with ${response.error}`);
}
return response;
}
@@ -0,0 +1,111 @@
import { readFileSync } from 'node:fs';
import type { Context, HttpRequest } from '@yaakapp/api';
import type { AccessToken, AccessTokenRawResponse, TokenStoreArgs } from './store';
import { deleteToken, getToken, storeToken } from './store';
import { isTokenExpired } from './util';
export async function getOrRefreshAccessToken(
ctx: Context,
tokenArgs: TokenStoreArgs,
{
scope,
accessTokenUrl,
credentialsInBody,
clientId,
clientSecret,
forceRefresh,
}: {
scope: string | null;
accessTokenUrl: string;
credentialsInBody: boolean;
clientId: string;
clientSecret: string;
forceRefresh?: boolean;
},
): Promise<AccessToken | null> {
const token = await getToken(ctx, tokenArgs);
if (token == null) {
return null;
}
const isExpired = isTokenExpired(token);
// Return the current access token if it's still valid
if (!isExpired && !forceRefresh) {
return token;
}
// Token is expired, but there's no refresh token :(
if (!token.response.refresh_token) {
return null;
}
// Access token is expired, so get a new one
const httpRequest: Partial<HttpRequest> = {
method: 'POST',
url: accessTokenUrl,
bodyType: 'application/x-www-form-urlencoded',
body: {
form: [
{ name: 'grant_type', value: 'refresh_token' },
{ name: 'refresh_token', value: token.response.refresh_token },
],
},
headers: [
{ name: 'User-Agent', value: 'yaak' },
{ name: 'Accept', value: 'application/x-www-form-urlencoded, application/json' },
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' },
],
};
if (scope) httpRequest.body?.form.push({ name: 'scope', value: scope });
if (credentialsInBody) {
httpRequest.body?.form.push({ name: 'client_id', value: clientId });
httpRequest.body?.form.push({ name: 'client_secret', value: clientSecret });
} else {
const value = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
httpRequest.headers?.push({ name: 'Authorization', value });
}
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
const resp = await ctx.httpRequest.send({ httpRequest });
if (resp.status >= 400 && resp.status < 500) {
// Client errors (4xx) indicate the refresh token is invalid, expired, or revoked
// Delete the token and return null to trigger a fresh authorization flow
console.log('[oauth2] Refresh token request failed with client error, deleting token');
await deleteToken(ctx, tokenArgs);
return null;
}
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
console.log('[oauth2] Got refresh token response', resp.status);
if (resp.status < 200 || resp.status >= 300) {
throw new Error(`Failed to refresh access token with status=${resp.status} and body=${body}`);
}
// biome-ignore lint/suspicious/noExplicitAny: none
let response: any;
try {
response = JSON.parse(body);
} catch {
response = Object.fromEntries(new URLSearchParams(body));
}
if (response.error) {
throw new Error(
`Failed to fetch access token with ${response.error} -> ${response.error_description}`,
);
}
const newResponse: AccessTokenRawResponse = {
...response,
// Assign a new one or keep the old one,
refresh_token: response.refresh_token ?? token.response.refresh_token,
};
return storeToken(ctx, tokenArgs, newResponse);
}

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