Compare commits

..

215 Commits

Author SHA1 Message Date
Gregory Schier
fdb4331032 Build plugins 2025-01-14 11:03:15 -08:00
Gregory Schier
8d645eb8c6 Fix sync setting truncation 2025-01-14 06:49:53 -08:00
Gregory Schier
592cf38e38 Fix yaak schema in export 2025-01-14 06:31:48 -08:00
Gregory Schier
ac0ecb342d Fix tab jiggle 2025-01-14 06:15:52 -08:00
Gregory Schier
439a29ab46 Update README 2025-01-14 06:15:46 -08:00
Gregory Schier
db64b54c79 Slight cleanup 2025-01-13 16:59:39 -08:00
Gregory Schier
49f5e980de Clean up filesystem sync setting 2025-01-13 16:46:56 -08:00
Gregory Schier
658e2179ca Add beta feedback badge 2025-01-13 16:16:01 -08:00
Gregory Schier
e7184e4d47 Fix releases lint 2025-01-13 12:32:20 -08:00
Gregory Schier
6719573b2b Preserve dropdown trigger button background when menu is open 2025-01-13 12:28:31 -08:00
Gregory Schier
4479164321 Merge remote-tracking branch 'origin/master' 2025-01-13 12:11:42 -08:00
Gregory Schier
4295a09515 Adjust settings window and fix dynamic dialog 2025-01-13 12:11:38 -08:00
Gregory Schier
bb5da84c82 Fix Windows/Linux CmdCtrl hotkey 2025-01-13 12:10:23 -08:00
Gregory Schier
72ab3f0a3c Better SegmentedControl styles 2025-01-13 11:08:48 -08:00
Gregory Schier
eea87ac02f Better SegmentedControl styles 2025-01-13 11:08:04 -08:00
Gregory Schier
587667fe79 Better markdown editor and SegmentedControl 2025-01-13 10:46:13 -08:00
Gregory Schier
84c3987c34 Fix unused var 2025-01-13 09:58:37 -08:00
Gregory Schier
40a77be556 Update plugins 2025-01-13 08:53:14 -08:00
Gregory Schier
d37cfad862 Fix var underscores 2025-01-13 07:41:13 -08:00
Gregory Schier
34c0449a40 Merge remote-tracking branch 'origin/master' 2025-01-13 07:15:13 -08:00
Gregory Schier
ad4d695b75 Fix environment activation and setting active cookie jar 2025-01-13 07:15:01 -08:00
OTonGitHub
969e1b965d fix grammer in readme (#154) 2025-01-13 06:40:03 -08:00
Gregory Schier
88ff7f4300 Support dashes in template variable/fn names 2025-01-13 06:38:21 -08:00
Gregory Schier
8cd9c031e8 Add back sidebar hover style 2025-01-12 21:17:29 -08:00
Gregory Schier
806ce2f0ba Fix request pane name styling 2025-01-11 14:33:00 -08:00
Gregory Schier
dcb17c3ed4 Comment 2025-01-11 14:18:16 -08:00
Gregory Schier
d2936cb022 Ensure only one dropdown can be open at a time 2025-01-11 14:16:37 -08:00
Gregory Schier
ba330047ca Cargo format 2025-01-11 13:53:30 -08:00
Gregory Schier
295aea4f2e Add single-instance plugin 2025-01-11 13:50:44 -08:00
Gregory Schier
476dbc432b Add FocusTrap to dropdown menu to fix filtering 2025-01-11 12:15:01 -08:00
Gregory Schier
8dff75ad4f Add ability to exclude environments from data export 2025-01-11 11:36:00 -08:00
Gregory Schier
88b410bf99 Fix pairs language not highlighting 2025-01-11 08:54:45 -08:00
Gregory Schier
3d3ff2824f Improve Dropdown selection handling 2025-01-11 08:15:45 -08:00
Gregory Schier
3b56f4e142 Support TLS 1.3 (#153) 2025-01-11 06:51:34 -08:00
Gregory Schier
576340db33 Improve initial sync subscription 2025-01-11 06:47:03 -08:00
Gregory Schier
bcf5b3db84 Fix dropdown not closing when clicking trigger 2025-01-11 06:46:47 -08:00
Gregory Schier
8b5b66acf0 Don't load response when blocking large responses 2025-01-10 06:27:57 -08:00
Gregory Schier
f694456ddc Fix workspace creation, reveal sync dir, and don't update timestamps on sync/import 2025-01-09 07:50:23 -08:00
Gregory Schier
0a7257c55a Fix redirecting to latest workspace 2025-01-08 22:08:59 -08:00
Gregory Schier
328e3db56e Fix UpdateSource for sync upserts 2025-01-08 15:25:03 -08:00
Gregory Schier
cbc443075a Ability to open workspace from directory, WorkspaceMeta, and many sync improvements 2025-01-08 14:57:13 -08:00
Gregory Schier
37671a50f2 Ensure fs_sync always writes to current state's path (even if Yaak changes the default) 2025-01-08 09:20:59 -08:00
Gregory Schier
95266a9177 Tweak workspace settings dialog and Markdown editor 2025-01-08 08:54:40 -08:00
Gregory Schier
eeb66ca28a Fix UrlBar padding 2025-01-08 06:43:45 -08:00
Gregory Schier
d745e91f80 Backspace to delete selected in sidebar 2025-01-08 06:42:32 -08:00
Gregory Schier
7a9c2e2223 Move info tab to last 2025-01-08 06:21:44 -08:00
Gregory Schier
1d51bd642a Menu now uses "click away" instead of backdrop 2025-01-08 06:20:21 -08:00
Gregory Schier
1920f720a9 Some small tweaks for Vim mode 2025-01-08 06:11:05 -08:00
Gregory Schier
81005165f3 Vim/emacs/vscode keybindings 2025-01-07 22:27:43 -08:00
Gregory Schier
3cf372c01e Fix workspace creation dialog 2025-01-07 07:05:23 -08:00
Gregory Schier
2f7b66fc92 A bunch of changes, including moving prompt/confirm out of context 2025-01-07 06:56:51 -08:00
Gregory Schier
4776bbc753 Fix more 2025-01-07 05:37:50 -08:00
Gregory Schier
79f668c863 Fix split layout not always working 2025-01-07 05:36:27 -08:00
Gregory Schier
a164875104 Move saved tab state to jotai with LS 2025-01-06 17:06:56 -08:00
Gregory Schier
bc50891edb Remove useNavigate everywhere, and make request a query param. And convert dialog to Jotai 2025-01-06 16:54:07 -08:00
Gregory Schier
806a8eb801 Remove useToast everywhere 2025-01-06 12:21:21 -08:00
Gregory Schier
ab55c2e0ce Move toast state to Jotai 2025-01-06 12:05:43 -08:00
Gregory Schier
c2ea2a5fe5 Dir sync filesystem watching 2025-01-06 09:24:07 -08:00
Gregory Schier
c72180bb59 Upgrade Tauri and add Tauri devtools 2025-01-05 11:33:39 -08:00
Gregory Schier
17fdd608d1 Optimize directory sync performance 2025-01-05 10:56:40 -08:00
Gregory Schier
40adce921b Move JS monorepo packages to folder 2025-01-04 16:55:18 -08:00
Gregory Schier
75ead9cc8a Fix error handling 2025-01-04 07:44:39 -08:00
Gregory Schier
609bd4cdea Update license message 2025-01-04 07:36:38 -08:00
Gregory Schier
68e1b5d746 Update license message 2025-01-04 07:32:42 -08:00
Gregory Schier
53f5ef3515 Add separate SVG response viewer 2025-01-04 07:14:33 -08:00
Gregory Schier
592c1228f1 Fix tauri event listener hook 2025-01-04 07:01:31 -08:00
Gregory Schier
36cecb2d29 Fix active workspace deletion 2025-01-03 20:50:18 -08:00
Gregory Schier
31440eea76 Filesystem Sync (#142) 2025-01-03 20:41:00 -08:00
Gregory Schier
6ad27c4458 Put delete workspace in settings 2025-01-02 08:41:44 -08:00
Gregory Schier
0dd09062e3 Sort workspaces, envs, jars 2025-01-02 08:33:04 -08:00
Gregory Schier
5ebf7dc499 Tackled remaining perf wins 2025-01-02 06:51:54 -08:00
Gregory Schier
42cd4a5f0f Split out slow pathParameters extension and skip unnecessary model updates 2025-01-01 16:42:53 -08:00
Gregory Schier
add39bda6e Revert to preserving editor state with fromJson due to state callbacks not being preserved 2025-01-01 08:19:41 -08:00
Gregory Schier
be938a81dc Fix settings update bug 2025-01-01 07:10:29 -08:00
Gregory Schier
4b807f221b A bunch of fixes 2025-01-01 07:01:41 -08:00
Gregory Schier
80119f6574 Prevent a bunch more stuff from re-rendering 2024-12-31 23:24:41 -08:00
Gregory Schier
dfca17f9b7 Prevent sidebar re-render on every keypress (#152) 2024-12-31 15:02:10 -08:00
Gregory Schier
135c366e32 Preserve Editor State (#151) 2024-12-31 07:31:43 -08:00
Gregory Schier
31f2bff0f6 Optimize sidebar collapsing 2024-12-23 05:05:04 -08:00
Gregory Schier
61d094d9fd Some fixes around environments 2024-12-21 11:04:49 -08:00
Gregory Schier
c1d5881167 Merge branch 'master' into cleanup
# Conflicts:
#	src-web/components/MarkdownEditor.tsx
#	src-web/components/RequestPane.tsx
#	src-web/hooks/useActiveEnvironment.ts
2024-12-21 05:46:33 -08:00
Gregory Schier
dd8ccfe21f Extract base environment (#149) 2024-12-21 05:44:55 -08:00
Gregory Schier
b4b29babfd Adjust markdown editor 2024-12-21 05:39:11 -08:00
Gregory Schier
ecabe9b6ef Cleanup (#148) 2024-12-20 23:49:48 -08:00
Gregory Schier
ec999015ab Fixed the circular imports and things 2024-12-20 23:49:15 -08:00
Gregory Schier
51a11b6495 Switch to useMutation in some places 2024-12-20 17:38:41 -08:00
Gregory Schier
27134a52ad Performance sweep (#147) 2024-12-20 17:31:15 -08:00
Gregory Schier
42bf016e90 Add the ability to duplicate folders (#144) 2024-12-19 13:06:08 -08:00
Gregory Schier
833dc7d3f7 Markdown documentation for HTTP requests (#145) 2024-12-19 05:57:40 -08:00
Gregory Schier
42d350ef27 Disallow drag-n-drop when editing request name 2024-12-17 06:22:29 -08:00
Gregory Schier
a81f9d07cb Tweak license message 2024-12-17 06:14:12 -08:00
Gregory Schier
cb6e3d4ac8 Better insight into settings updates 2024-12-16 16:27:13 -08:00
Gregory Schier
5ff5d6fb1d Update release info 2024-12-16 13:53:45 -08:00
Gregory Schier
e2253786dc Tweak license flow 2024-12-16 13:46:58 -08:00
Gregory Schier
20140148bf Update gruvbox theme 2024-12-13 06:38:54 -08:00
Albert Hansrisuk
4b9dce26ac feat: add gruvbox dark theme (#140) 2024-12-13 06:30:10 -08:00
Jeffrey Mitchell
3b2c2960a9 Fix: Settings Window Title Bar Draggable Region Deadzone (#139) 2024-12-09 20:58:55 -08:00
Gregory Schier
a79578142d Port some stuff from sync PR 2024-12-05 11:27:49 -08:00
Gregory Schier
2b61257e50 Fix date 2024-12-03 11:48:39 -08:00
Gregory Schier
1f4eea89c5 Better license badge placement 2024-12-03 11:06:50 -08:00
Gregory Schier
1609e46660 Fix CI 2024-12-03 10:58:39 -08:00
Gregory Schier
28d5a2a019 Add copy 2024-12-03 10:09:44 -08:00
Gregory Schier
40f0f5387a Fix lint problems 2024-12-03 09:43:16 -08:00
Gregory Schier
88bcfb9e66 Changes for commercial use (#138) 2024-12-03 09:28:27 -08:00
Gregory Schier
2b076c90e4 Increase gRPC max message sizes 2024-12-03 09:24:14 -08:00
Leandro Otoni
0443fbdfdb Merge pull request #137
* add codeFolding config for json
2024-12-03 07:15:38 -08:00
Gregory Schier
36d24bdac0 Tweaked header size logic 2024-12-03 07:11:25 -08:00
Peiman Nourani
d4dfc1c820 Hide window controls in macOS fullscreen mode (#134) 2024-12-03 07:08:01 -08:00
Bad3r
00178ad197 Feat: Add Support for Nord Theme 🏔️ (#132)
Signed-off-by: Bad3r <bad3r@pm.me>
2024-12-03 07:00:47 -08:00
Bad3r
f8efd1a31a fix: Add Missing Themes Moonlight & Dracula (#133)
Signed-off-by: Bad3r <bad3r@pm.me>
2024-12-03 06:57:33 -08:00
Gregory Schier
e1363cf151 Make protoc writable before signing 2024-11-23 06:18:17 -08:00
Gregory Schier
38e0f5ede7 Remove unnecessary things in Windows signing conf 2024-11-23 05:27:12 -08:00
Gregory Schier
9663018e21 Update tauri.conf.json 2024-11-22 08:51:29 -08:00
Gregory Schier
80a7c2a9c7 Try updating protoc 2024-11-22 07:09:59 -08:00
Gregory Schier
4687723176 Fix Tauri signing account name 2024-11-22 06:17:37 -08:00
Gregory Schier
41ce2df00c Move signing into Tauri 2024-11-22 05:49:24 -08:00
Gregory Schier
794967904a Try signing again 2024-11-21 13:43:05 -08:00
Gregory Schier
74a7a1a21a Merge remote-tracking branch 'origin/master' 2024-11-21 13:13:10 -08:00
Gregory Schier
d9587aa314 First attempt at Windows signing 2024-11-21 13:13:05 -08:00
dependabot[bot]
6b208ef67c Bump cross-spawn (#135)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 21:15:30 -08:00
Gregory Schier
0cfec0ada6 Fix GraphQL body handling 2024-11-18 07:17:19 -08:00
Gregory Schier
3ecfb15c89 Fix lint 2024-11-16 15:31:14 -08:00
Gregory Schier
23c026126f Tweak schema menu 2024-11-16 15:17:26 -08:00
Gregory Schier
ff9abab547 More control over GraphQL introspection 2024-11-16 14:27:13 -08:00
Gregory Schier
c9c48c77e4 Update Tauri deps 2024-11-16 13:56:32 -08:00
Hao Xiang
83efc58f29 don't lost request's name and folder when updated by curl (#131) 2024-11-14 14:03:22 -08:00
Gregory Schier
632e1ff091 Update README.md 2024-11-12 19:31:55 -08:00
Gregory Schier
40286756b9 Update README.md 2024-11-11 07:48:39 -08:00
Hao Xiang
1050ac5e3c fix(grpc): proto dep topo order to solve panic (#130) 2024-10-29 14:19:11 -07:00
Gregory Schier
6d2c3712c0 Fix active cookie jar and improve routing 2024-10-28 10:06:43 -07:00
Gregory Schier
4a52095033 Better template function fetching 2024-10-24 08:17:58 -07:00
Gregory Schier
55b12d7329 Try fix for template tags not re-fetching on Windows 2024-10-24 07:47:20 -07:00
Gregory Schier
f4240e5229 Prevent bg flash on context menu in sidebar 2024-10-23 10:07:31 -07:00
Gregory Schier
7759649963 Update local model stores in all mutations (#129) 2024-10-23 09:54:43 -07:00
Gregory Schier
c5e6d6f2cb Some tweaks to request deletion 2024-10-23 06:27:38 -07:00
Gregory Schier
ec850f2cf0 Properly handle charset in content-type 2024-10-23 05:49:14 -07:00
Gregory Schier
ff52ad5345 Handle quotes around charset 2024-10-23 05:44:37 -07:00
Gregory Schier
5de50c70c6 Fix workspace/request creation race 2024-10-22 14:27:12 -07:00
Gregory Schier
94f8949ca2 Fix formatter < 0 error 2024-10-22 14:26:45 -07:00
Gregory Schier
44fc3c8d2d Add formatter test for escaped characters 2024-10-22 08:11:03 -07:00
Gregory Schier
57a05d5486 Fix up some of the new formatting stuff 2024-10-22 08:07:56 -07:00
Gregory Schier
e216214085 Custom JSON formatter that works with template syntax (#128) 2024-10-21 15:17:14 -07:00
Gregory Schier
aa7f18a16f Order cmd+k results by match score 2024-10-21 12:54:15 -07:00
Gregory Schier
b9f397e04a Fix response filtering 2024-10-21 07:26:50 -07:00
Gregory Schier
57c3a86799 Animate up instead of down when dropdowns open up 2024-10-18 11:22:05 -07:00
Gregory Schier
52ac41b0c6 Move elapsed calculation 2024-10-18 10:53:04 -07:00
Gregory Schier
741ccbe741 Add labels to plugin event subscribers 2024-10-18 10:46:30 -07:00
Gregory Schier
2ecd86da78 Update README.md 2024-10-18 08:27:24 -07:00
Gregory Schier
30e4e7665a Remove ios config 2024-10-18 07:59:28 -07:00
Gregory Schier
516dfd1f19 Fix GraphQL introspection 2024-10-18 06:57:44 -07:00
Gregory Schier
0cd08499aa Render sending gRPC events 2024-10-17 12:03:35 -07:00
Gregory Schier
c652df82a3 Fix SSE event selection 2024-10-17 11:28:10 -07:00
Gregory Schier
c8342fb0a9 Delete send history for workspace 2024-10-17 11:17:27 -07:00
Gregory Schier
d0b59a0fb4 Show folder structure in request selection 2024-10-17 10:53:48 -07:00
Gregory Schier
6f50f35519 Bump Tauri to fix macOS 13 launch issue 2024-10-15 09:54:21 -07:00
Gregory Schier
4e775b2b49 Undo minimumSystemVersion 2024-10-15 07:49:27 -07:00
Gregory Schier
e77a9e5d44 Rebuild plugins 2024-10-15 07:48:26 -07:00
Gregory Schier
a381e44d8c Prevent stale content flash after editing request name 2024-10-15 07:32:00 -07:00
Gregory Schier
4acf0969e8 Only sync models from active workspace 2024-10-15 07:31:42 -07:00
Gregory Schier
30c4178269 Disable autocomplete/correct/etc in plain input 2024-10-14 21:46:48 -07:00
Gregory Schier
dffe6e0a16 Intelligent readonly editor updates, to preserve scroll 2024-10-14 10:40:09 -07:00
Gregory Schier
8090e67b9e Revert hyper v1 for gRPC 2024-10-12 22:05:17 -07:00
Gregory Schier
f1beabcb6f Try again 2024-10-12 21:33:45 -07:00
Gregory Schier
647b8e2313 Try fix windows build 2024-10-12 21:17:44 -07:00
Gregory Schier
f5b4697608 Npm i 2024-10-12 21:06:19 -07:00
Gregory Schier
f201857d51 Bump Tauri to fix settings window 2024-10-12 20:57:01 -07:00
Gregory Schier
0d982057a5 Add proxy setting for HTTP requests (#127) 2024-10-12 20:55:09 -07:00
Gregory Schier
6fb94384b9 Better fuzzy matching in cmd palette 2024-10-12 07:41:01 -07:00
Gregory Schier
d754e7233d Server sent event response viewer (#126) 2024-10-11 06:52:32 -07:00
Gregory Schier
f974a66086 Fix double-click-maximize and backdrop 2024-10-10 07:11:43 -07:00
Gregory Schier
250625fc0e Always show window controls, and open Linux settings in dialog 2024-10-10 06:22:11 -07:00
Gregory Schier
16e090b520 Fix content type detection 2024-10-09 17:20:09 -07:00
Gregory Schier
be9fbbcb6e Fix content type detection 2024-10-09 17:19:41 -07:00
Gregory Schier
8be3c3d0e1 Disable response copy until response is done 2024-10-09 16:38:12 -07:00
Gregory Schier
c680e15cb5 Max width on request name in header 2024-10-09 16:38:03 -07:00
Gregory Schier
da6baf72f5 Response Streaming (#124) 2024-10-09 16:27:37 -07:00
Gregory Schier
2ca30bcb31 Fix Codemirror undo history 2024-10-09 12:00:52 -07:00
Gregory Schier
2e2b3128c5 Fix cookie jar query 2024-10-09 11:26:19 -07:00
Gregory Schier
4a81818d05 Add descriptions to template functions 2024-10-09 11:25:51 -07:00
Gregory Schier
0eb98a3882 Log query errors 2024-10-09 11:25:05 -07:00
Gregory Schier
d28100d682 Add new plugins 2024-10-09 09:54:15 -07:00
Gregory Schier
0f4d3bdbb5 Allow space in dropdown filter text 2024-10-09 09:54:07 -07:00
Gregory Schier
c7eccddac9 Fix performance related to having 100s of requests (#123) 2024-10-08 14:16:57 -07:00
Gregory Schier
4b7712df80 Better Dropdown size calculation for scrolling when not enough room 2024-10-02 16:17:28 -07:00
Gregory Schier
e5c6c31e02 Fix prompt again 2024-10-02 12:53:58 -07:00
Gregory Schier
7e62bb6b68 Fix prompt 2024-10-02 12:19:43 -07:00
Gregory Schier
3b2ee25d75 Fix tauri dialog API usage 2024-10-02 11:45:42 -07:00
Gregory Schier
4a9e2ac9b6 Specify minimum macOS version 13 in tauri conf 2024-10-02 11:39:48 -07:00
Gregory Schier
d890b8be0a Tauri 2.0.0 stable 2024-10-02 11:30:39 -07:00
Gregory Schier
bb1ba93676 Better KeyValueRow sizing 2024-10-02 11:22:20 -07:00
Gregory Schier
f8c3f71cfe Increase node download timeout 2024-10-02 10:14:51 -07:00
Gregory Schier
c9050bd3bb Add CLI to release.yml 2024-10-02 10:04:21 -07:00
Gregory Schier
d0fe1beee0 Fix lint errors 2024-10-02 10:00:58 -07:00
Gregory Schier
e401e8f1cf Fix response header table sizing 2024-10-02 08:22:38 -07:00
Gregory Schier
4160e5b1c4 Make prompt() to return null on cancel 2024-10-02 05:54:44 -07:00
Gregory Schier
89ff25cd54 Fix variables in bulk editor 2024-10-02 05:10:34 -07:00
Gregory Schier
7a941016a9 Fix plugin types 2024-10-01 11:02:38 -07:00
Gregory Schier
7e4f807f75 Add prompt() plugin API (#121) 2024-10-01 08:32:42 -07:00
Gregory Schier
be60e4648a Apply Request path parameters during render (#120) 2024-10-01 08:26:59 -07:00
Gregory Schier
6060ddcd87 Add aliases field to template functions 2024-09-30 18:28:52 -07:00
Gregory Schier
9915c57817 Plugin execution context (#119) 2024-09-30 17:45:51 -07:00
Gregory Schier
917adcfb2e Better plugin development experience (#118) 2024-09-29 10:41:07 -07:00
Gregory Schier
1c5e62a468 Set max-width on toast 2024-09-29 07:20:56 -07:00
Gregory Schier
f5e8c525e9 Add brackets to URL regex 2024-09-28 14:31:18 -07:00
Gregory Schier
6583615885 Update package.json 2024-09-27 06:01:31 -07:00
Gregory Schier
0f0eba244c Update config.yml 2024-09-27 05:59:23 -07:00
Gregory Schier
3911b7b583 Update README.md 2024-09-27 05:58:29 -07:00
Gregory Schier
3634d315ed Update config.yml 2024-09-27 05:49:58 -07:00
Gregory Schier
89b85c2e27 Update issue description 2024-09-27 05:49:20 -07:00
Gregory Schier
bbff3f5969 Remove bug report template 2024-09-27 05:47:24 -07:00
Gregory Schier
eb8f66dca2 Add npm start command back 2024-09-27 05:46:12 -07:00
Gregory Schier
08531fa1ff Update development doc 2024-09-27 05:45:48 -07:00
i-usebruno
662bc41cb3 Improve dev docs (#117)
Co-authored-by: i-usebruno <anon.repressed498@passinbox.com>
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
2024-09-27 05:34:15 -07:00
Gregory Schier
974425afee Fix JSONPath/XPath filtering 2024-09-26 06:45:03 -07:00
Gregory Schier
9f7f06a142 Don't adhere to wrapLines in single-line inputs 2024-09-25 22:16:35 -07:00
466 changed files with 197108 additions and 17308 deletions

View File

@@ -8,14 +8,15 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'eslint-config-prettier',
],
plugins: ['react-refresh'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.json'],
},
ignorePatterns: [
'scripts/**/*',
'plugin-runtime/**/*',
'plugin-runtime-types/**/*',
'packages/plugin-runtime/**/*',
'packages/plugin-runtime-types/**/*',
'src-tauri/**/*',
'src-web/tailwind.config.cjs',
'src-web/vite.config.ts',
@@ -32,6 +33,7 @@ module.exports = {
},
},
rules: {
'react-refresh/only-export-components': 'error',
'jsx-a11y/no-autofocus': 'off',
'react/react-in-jsx-scope': 'off',
'import/no-unresolved': 'off',

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
src-tauri/vendored/**/* linguist-generated=true
src-tauri/gen/schemas/**/* linguist-generated=true

View File

@@ -1,45 +0,0 @@
name: Bug Report
description: "Something isn't working properly in Yaak"
title: "Short description"
labels: ["bug", "needs triage"]
assignees:
- gschier
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report 🤗
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of our software are you running?
placeholder: 2024.8.0
validations:
required: true
- type: dropdown
id: os
attributes:
label: What operating system are you on?
multiple: false
options:
- macOS
- Windows
- Linux
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request, Question, etc.
- name: Bugs, Feedback, Feature Requests, and Questions
url: https://feedback.yaak.app
about: Report all non-bugs to the feedback board 👉🏼
about: "Please report to Yaak's public feedback board. Issues will be created and linked here when applicable."

View File

@@ -22,7 +22,7 @@ jobs:
- platform: 'macos-latest' # for Intel-based Macs.
args: '--target x86_64-apple-darwin'
yaak_arch: 'x64'
- platform: 'ubuntu-22.04' # for Tauri v1, you could replace this with ubuntu-20.04.
- platform: 'ubuntu-22.04'
args: ''
yaak_arch: 'x64'
- platform: 'windows-latest'
@@ -38,10 +38,6 @@ jobs:
with:
node-version: 22
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -56,7 +52,7 @@ jobs:
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
src-tauri/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
@@ -66,15 +62,23 @@ jobs:
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
- 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: Run lint
run: npm run lint
@@ -93,20 +97,32 @@ jobs:
env:
YAAK_PLUGINS_DIR: ${{ env.YAAK_PLUGINS_DIR }}
YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_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 }}
# 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 }}
with:
tagName: 'v__VERSION__'
releaseName: 'Release __VERSION__'
releaseBody: 'https://yaak.app/blog/__VERSION__'
releaseBody: |
> [!IMPORTANT]
> The Yaak project is open source. However, to fund development, these pre-built binaries require a license for commercial use. Please see the [Pricing Page](https://yaak.app/pricing) for more details.
You can view the full release notes here → https://yaak.app/blog/2024.13.0
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}

View File

@@ -1,19 +1,62 @@
## Developer Setup
# Developer Setup
Development requires the following tools
Yaak is a combined Node.js and Rust monorepo. It is a [Tauri](https://tauri.app) project, so
uses Rust and HTML/CSS/JS for the main application but there is also a plugin system powered
by a Node.js sidecar that communicates to the app over gRPC.
Because of the moving parts, there are a few setup steps required before development can
begin.
## Prerequisites
Make sure you have the following tools installed:
- [Node.js](https://nodejs.org/en/download/package-manager)
- [Rust](https://www.rust-lang.org/tools/install)
Then, you can run the app.
Check the installations with the following commands:
1. Checkout the [plugins](https://github.com/yaakapp/plugins) repository
2. Run `YAAK_PLUGINS_DIR="..." npm run bootstrap` to fetch external binaries, build local dependencies, etc.
3. Run the desktop app in dev mode `npm start`
```shell
node -v
npm -v
rustc --version
```
Install the NPM dependencies:
```shell
npm install
```
Run the `bootstrap` command to do some initial setup:
```shell
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:
```shell
npm start
```
_NOTE: If working on bundled plugins, run with `YAAK_PLUGINS_DIR=<Path to yaakapp/plugins>`_
## SQLite Migrations
1. From `src-tauri/`, run `sqlx migrate add migration-name`
2. Migrate the DB by running the app (may need to `cargo clean` first)
New migrations can be created from the `src-tauri/` directory:
```shell
cd src-tauri
sqlx migrate add migration-name
```
_Note: Yaak development builds use a separate database location than production releases_
Run the app to apply the migrations.
If nothing happens, try `cargo clean` and run the app again.
_Note: Development builds use a separate database location from production builds._

View File

@@ -1,4 +1,4 @@
# [Yaak API Client](https://yaak.app)
# Yaak API Client
Yaak is a desktop API client for organizing and executing REST, GraphQL, and gRPC
requests. It's built using [Tauri](https://tauri.app), Rust, and ReactJS.
@@ -7,10 +7,20 @@ requests. It's built using [Tauri](https://tauri.app), Rust, and ReactJS.
## Feedback and Bug Reports
Please [Create an Issue](https://github.com/yaakapp/app/issues/new) for bug reports and
submit all other feedback to the [Feedback Board](https://feedback.yaak.app).
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.
## Community Projects
- [`yaak2postman`](https://github.com/BiteCraft/yaak2postman) CLI for converting Yaak data
exports to Postman-compatible collections
## Contribution Policy
Yaak open source, but currently only accepting contributions for bug fixes. See [
`DEVELOPMENT.md`](DEVELOPMENT.md).
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.
To get started, visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your
environment.

5365
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,21 +4,26 @@
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yaakapp/app.git"
"url": "git+https://github.com/mountain-loop/yaak.git"
},
"workspaces": [
"plugin-runtime",
"plugin-runtime-types",
"src-tauri/yaak_models",
"src-tauri/yaak_plugin_runtime",
"src-tauri/yaak_sync",
"src-tauri/yaak_templates",
"packages/plugin-runtime",
"packages/plugin-runtime-types",
"packages/common-lib",
"src-tauri/yaak-license",
"src-tauri/yaak-models",
"src-tauri/yaak-plugins",
"src-tauri/yaak-sse",
"src-tauri/yaak-sync",
"src-tauri/yaak-templates",
"src-web"
],
"scripts": {
"start": "npm run app-dev",
"app-build": "tauri build",
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
"bootstrap": "run-p bootstrap:* && npm run --workspace plugin-runtime build",
"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",
@@ -29,18 +34,18 @@
"tauri-before-dev": "npm run --workspaces --if-present dev"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0-rc.16",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"@tauri-apps/cli": "^2.2.2",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^8",
"eslint-config-prettier": "^8",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.35.2",
"eslint-plugin-react-hooks": "^4.6.2",
"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",
"nodejs-file-downloader": "^4.13.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"typescript": "^5.6.2"
"prettier": "^3.4.2",
"typescript": "^5.7.2"
}
}

View File

@@ -0,0 +1 @@
export * from './debounce';

View File

@@ -1,5 +1,5 @@
{
"name": "@yaakapp-internal/plugin",
"name": "@yaakapp-internal/lib",
"private": true,
"version": "1.0.0",
"main": "index.ts"

View File

@@ -1,15 +1,18 @@
{
"name": "@yaakapp/api",
"version": "0.2.7",
"version": "0.2.17",
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"files": [
"lib/**/*"
],
"scripts": {
"bootstrap": "npm run build",
"build": "run-s build:copy-types build:tsc",
"build:tsc": "tsc",
"build:copy-types": "cpy --flat ../src-tauri/yaak_plugin_runtime/bindings/*.ts ./src/bindings/",
"build:copy-types": "run-p build:copy-types:*",
"build:copy-types:root": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/*.ts ./src/bindings",
"build:copy-types:next": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/serde_json/*.ts ./src/bindings/serde_json",
"prepublishOnly": "npm run build"
},
"dependencies": {

View File

@@ -0,0 +1,338 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type {
Environment,
Folder,
GrpcRequest,
HttpRequest,
HttpResponse,
Workspace,
} from './models';
import type { JsonValue } from './serde_json/JsonValue';
export type BootRequest = { dir: string; watch: boolean };
export type BootResponse = { name: string; version: string; capabilities: Array<string> };
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest };
export type CallHttpRequestActionRequest = {
key: string;
pluginRefId: string;
args: CallHttpRequestActionArgs;
};
export type CallTemplateFunctionArgs = {
purpose: RenderPurpose;
values: { [key in string]?: string };
};
export type CallTemplateFunctionRequest = { name: string; args: CallTemplateFunctionArgs };
export type CallTemplateFunctionResponse = { value: string | null };
export type Color =
| 'custom'
| 'default'
| 'primary'
| 'secondary'
| 'info'
| 'success'
| 'notice'
| 'warning'
| 'danger';
export type CopyTextRequest = { text: string };
export type ExportHttpRequestRequest = { httpRequest: HttpRequest };
export type ExportHttpRequestResponse = { content: string };
export type FilterRequest = { content: string; filter: string };
export type FilterResponse = { content: string };
export type FindHttpResponsesRequest = { requestId: string; limit?: number };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse> };
export type GetHttpRequestActionsRequest = Record<string, never>;
export type GetHttpRequestActionsResponse = {
actions: Array<HttpRequestAction>;
pluginRefId: string;
};
export type GetHttpRequestByIdRequest = { id: string };
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null };
export type GetTemplateFunctionsResponse = {
functions: Array<TemplateFunction>;
pluginRefId: string;
};
export type HttpRequestAction = { key: string; label: string; icon?: Icon };
export type Icon = 'copy' | 'info' | 'check_circle' | 'alert_triangle' | '_unknown';
export type ImportRequest = { content: string };
export type ImportResources = {
workspaces: Array<Workspace>;
environments: Array<Environment>;
folders: Array<Folder>;
httpRequests: Array<HttpRequest>;
grpcRequests: Array<GrpcRequest>;
};
export type ImportResponse = { resources: ImportResources };
export type InternalEvent = {
id: string;
pluginRefId: string;
replyId: string | null;
payload: InternalEventPayload;
windowContext: WindowContext;
};
export type InternalEventPayload =
| ({ type: 'boot_request' } & BootRequest)
| ({ type: 'boot_response' } & BootResponse)
| { type: 'reload_request' }
| { type: 'reload_response' }
| { 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' } & GetHttpRequestActionsRequest)
| ({ 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: 'copy_text_request' } & CopyTextRequest)
| ({ type: 'render_http_request_request' } & RenderHttpRequestRequest)
| ({ type: 'render_http_request_response' } & RenderHttpRequestResponse)
| ({ type: 'template_render_request' } & TemplateRenderRequest)
| ({ type: 'template_render_response' } & TemplateRenderResponse)
| ({ type: 'show_toast_request' } & ShowToastRequest)
| ({ 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' };
export type OpenFileFilter = {
name: string;
/**
* File extensions to require
*/
extensions: Array<string>;
};
export type PromptTextRequest = {
id: string;
title: string;
label: string;
description?: string;
defaultValue?: string;
placeholder?: string;
/**
* Text to add to the confirmation button
*/
confirmText?: string;
/**
* Text to add to the cancel button
*/
cancelText?: string;
/**
* Require the user to enter a non-empty value
*/
require?: boolean;
};
export type PromptTextResponse = { value: string | null };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest; purpose: RenderPurpose };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest };
export type RenderPurpose = 'send' | 'preview';
export type SendHttpRequestRequest = { httpRequest: HttpRequest };
export type SendHttpRequestResponse = { httpResponse: HttpResponse };
export type ShowToastRequest = { message: string; color?: Color; icon?: Icon };
export type TemplateFunction = {
name: string;
description?: string;
/**
* Also support alternative names. This is useful for not breaking existing
* tags when changing the `name` property
*/
aliases?: Array<string>;
args: Array<TemplateFunctionArg>;
};
export type TemplateFunctionArg =
| ({ type: 'text' } & TemplateFunctionTextArg)
| ({
type: 'select';
} & TemplateFunctionSelectArg)
| ({ type: 'checkbox' } & TemplateFunctionCheckboxArg)
| ({
type: 'http_request';
} & TemplateFunctionHttpRequestArg)
| ({ type: 'file' } & TemplateFunctionFileArg);
export type TemplateFunctionBaseArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionCheckboxArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionFileArg = {
/**
* The title of the file selection window
*/
title: string;
/**
* Allow selecting multiple files
*/
multiple?: boolean;
directory?: boolean;
defaultPath?: string;
filters?: Array<OpenFileFilter>;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionHttpRequestArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionSelectArg = {
/**
* The options that will be available in the select input
*/
options: Array<TemplateFunctionSelectOption>;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionSelectOption = { label: string; value: string };
export type TemplateFunctionTextArg = {
/**
* Placeholder for the text input
*/
placeholder?: string;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateRenderRequest = { data: JsonValue; purpose: RenderPurpose };
export type TemplateRenderResponse = { data: JsonValue };
export type WindowContext = { type: 'none' } | { type: 'label'; label: string };

View File

@@ -1,23 +1,25 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, };
export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, sortPriority: number, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, };
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: 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>, 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<GrpcMetadataEntry>, 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, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
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>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, url: string, version: string | null, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
export type HttpResponseHeader = { name: string, value: string, };
export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array<EnvironmentVariable>, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };

View File

@@ -0,0 +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 };

View File

@@ -1,13 +1,17 @@
import {
import type {
FindHttpResponsesRequest,
FindHttpResponsesResponse,
GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse,
PromptTextRequest,
PromptTextResponse,
RenderHttpRequestRequest,
RenderHttpRequestResponse,
SendHttpRequestRequest,
SendHttpRequestResponse,
ShowToastRequest,
TemplateRenderRequest,
TemplateRenderResponse,
} from '..';
export type Context = {
@@ -17,6 +21,9 @@ export type Context = {
toast: {
show(args: ShowToastRequest): void;
};
prompt: {
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
};
httpRequest: {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
@@ -25,4 +32,7 @@ export type Context = {
httpResponse: {
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
};
templates: {
render(args: TemplateRenderRequest): Promise<TemplateRenderResponse['data']>;
};
};

View File

@@ -1,4 +1,4 @@
import { Context } from './Context';
import type { Context } from './Context';
export type FilterPluginResponse = string[];

View File

@@ -1,5 +1,5 @@
import { CallHttpRequestActionArgs, HttpRequestAction } from '..';
import { Context } from './Context';
import type { CallHttpRequestActionArgs, HttpRequestAction } from '..';
import type { Context } from './Context';
export type HttpRequestActionPlugin = HttpRequestAction & {
onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void;

View File

@@ -1,12 +1,13 @@
import { Environment, Folder, HttpRequest, Workspace } from '..';
import { AtLeast } from '../helpers';
import { Context } from './Context';
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '..';
import type { AtLeast } from '../helpers';
import type { Context } from './Context';
export type ImportPluginResponse = null | {
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
httpRequests: AtLeast<HttpRequest, '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'>[];
};
export type ImporterPlugin = {

View File

@@ -1,5 +1,5 @@
import { CallTemplateFunctionArgs, TemplateFunction } from '..';
import { Context } from './Context';
import type { CallTemplateFunctionArgs, TemplateFunction } from '..';
import type { Context } from './Context';
export type TemplateFunctionPlugin = TemplateFunction & {
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;

View File

@@ -1,5 +1,5 @@
import { Theme } from '../themes';
import { Context } from './Context';
import type { Theme } from '../themes';
import type { Context } from './Context';
export type ThemePlugin = {
name: string;

View File

@@ -1,8 +1,8 @@
import { FilterPlugin } from './FilterPlugin';
import { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
import { ImporterPlugin } from './ImporterPlugin';
import { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import { ThemePlugin } from './ThemePlugin';
import type { FilterPlugin } from './FilterPlugin';
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';

View File

@@ -1,10 +1,11 @@
{
"name": "@yaakapp-internal/plugin-runtime",
"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:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../proto ../proto/plugins/*.proto"
"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:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../../proto ../../proto/plugins/*.proto"
},
"dependencies": {
"intercept-stdout": "^0.1.2",

View File

@@ -1,6 +1,6 @@
import { InternalEvent } from '@yaakapp/api';
import type { InternalEvent } from '@yaakapp/api';
import EventEmitter from 'node:events';
import { EventStreamEvent } from './gen/plugins/runtime';
import type { EventStreamEvent } from './gen/plugins/runtime';
export class EventChannel {
emitter: EventEmitter = new EventEmitter();

View File

@@ -1,14 +1,15 @@
import { InternalEvent } from '@yaakapp/api';
import type { BootRequest, InternalEvent } from '@yaakapp-internal/plugins';
import path from 'node:path';
import { Worker } from 'node:worker_threads';
import { EventChannel } from './EventChannel';
import type { EventChannel } from './EventChannel';
import type { PluginWorkerData } from './index.worker';
export class PluginHandle {
#worker: Worker;
constructor(
readonly pluginDir: string,
readonly pluginRefId: string,
readonly bootRequest: BootRequest,
readonly events: EventChannel,
) {
this.#worker = this.#createWorker();
@@ -24,28 +25,32 @@ export class PluginHandle {
#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: { pluginDir: this.pluginDir, pluginRefId: this.pluginRefId },
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.pluginDir);
console.log('Created plugin worker for ', this.bootRequest.dir);
return worker;
}
async #handleError(err: Error) {
console.error('Plugin errored', this.pluginDir, err);
console.error('Plugin errored', this.bootRequest.dir, err);
}
async #handleExit(code: number) {
if (code === 0) {
console.log('Plugin exited successfully', this.pluginDir);
console.log('Plugin exited successfully', this.bootRequest.dir);
} else {
console.log('Plugin exited with status', code, this.pluginDir);
console.log('Plugin exited with status', code, this.bootRequest.dir);
}
}
}

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc-gen-ts_proto v2.2.3
// protoc v3.19.1
// source: plugins/runtime.proto
@@ -33,13 +33,14 @@ export const EventStreamEvent: MessageFns<EventStreamEvent> = {
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
case 1: {
if (tag !== 10) {
break;
}
message.event = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;

View File

@@ -1,12 +1,16 @@
import { InternalEvent } from '@yaakapp/api';
import type { InternalEvent } from '@yaakapp/api';
import { createChannel, createClient, Status } from 'nice-grpc';
import { EventChannel } from './EventChannel';
import { PluginRuntimeClient, PluginRuntimeDefinition } from './gen/plugins/runtime';
import type { PluginRuntimeClient} from './gen/plugins/runtime';
import { PluginRuntimeDefinition } from './gen/plugins/runtime';
import { PluginHandle } from './PluginHandle';
const port = process.env.PORT || '50051';
const channel = createChannel(`localhost:${port}`);
const channel = createChannel(`localhost:${port}`, undefined, {
'grpc.max_receive_message_length': Number.MAX_SAFE_INTEGER,
'grpc.max_send_message_length': Number.MAX_SAFE_INTEGER,
});
const client: PluginRuntimeClient = createClient(PluginRuntimeDefinition, channel);
const events = new EventChannel();
@@ -18,7 +22,7 @@ const plugins: Record<string, PluginHandle> = {};
const pluginEvent: InternalEvent = JSON.parse(e.event);
// Handle special event to bootstrap plugin
if (pluginEvent.payload.type === 'boot_request') {
const plugin = new PluginHandle(pluginEvent.payload.dir, pluginEvent.pluginRefId, events);
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, events);
plugins[pluginEvent.pluginRefId] = plugin;
}

View File

@@ -1,26 +1,39 @@
import {
Context,
import type {
BootRequest,
FindHttpResponsesResponse,
GetHttpRequestByIdResponse,
HttpRequestAction,
ImportResponse,
InternalEvent,
InternalEventPayload,
PromptTextResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse,
TemplateFunction,
} from '@yaakapp/api';
import { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestActionPlugin';
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
TemplateRenderResponse,
WindowContext,
} from '@yaakapp-internal/plugins';
import type { Context } from '@yaakapp/api';
import type { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestActionPlugin';
import type { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
import interceptStdout from 'intercept-stdout';
import * as console from 'node:console';
import { Stats, readFileSync, statSync, watch } from 'node:fs';
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, workerData } from 'node:worker_threads';
export interface PluginWorkerData {
bootRequest: BootRequest;
pluginRefId: string;
}
async function initialize() {
const { pluginDir, pluginRefId } = workerData;
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');
@@ -42,21 +55,26 @@ async function initialize() {
if (typeof mod.pluginHookImport === 'function') capabilities.push('import');
if (typeof mod.pluginHookResponseFilter === 'function') capabilities.push('filter');
console.log('Plugin initialized', pkg.name, capabilities, Object.keys(mod));
console.log('Plugin initialized', pkg.name, { capabilities, enableWatch });
function buildEventToSend(
windowContext: WindowContext,
payload: InternalEventPayload,
replyId: string | null = null,
): InternalEvent {
return { pluginRefId, id: genId(), replyId, payload };
return { pluginRefId, id: genId(), replyId, payload, windowContext };
}
function sendEmpty(replyId: string | null = null): string {
return sendPayload({ type: 'empty_response' }, replyId);
function sendEmpty(windowContext: WindowContext, replyId: string | null = null): string {
return sendPayload(windowContext, { type: 'empty_response' }, replyId);
}
function sendPayload(payload: InternalEventPayload, replyId: string | null): string {
const event = buildEventToSend(payload, replyId);
function sendPayload(
windowContext: WindowContext,
payload: InternalEventPayload,
replyId: string | null,
): string {
const event = buildEventToSend(windowContext, payload, replyId);
sendEvent(event);
return event.id;
}
@@ -69,10 +87,11 @@ async function initialize() {
}
async function sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
windowContext: WindowContext,
payload: InternalEventPayload,
): Promise<T> {
// 1. Build event to send
const eventToSend = buildEventToSend(payload, null);
const eventToSend = buildEventToSend(windowContext, payload, null);
// 2. Spawn listener in background
const promise = new Promise<InternalEventPayload>(async (resolve) => {
@@ -97,53 +116,93 @@ async function initialize() {
}
// Reload plugin if JS or package.json changes
const windowContextNone: WindowContext = { type: 'none' };
const cb = async () => {
await reloadModule();
return sendPayload({ type: 'reload_response' }, null);
return sendPayload(windowContextNone, { type: 'reload_response' }, null);
};
watchFile(pathMod, cb);
watchFile(pathPkg, cb);
if (enableWatch) {
watchFile(pathMod, cb);
watchFile(pathPkg, cb);
}
const ctx: Context = {
const newCtx = (event: InternalEvent): Context => ({
clipboard: {
async copyText(text) {
await sendAndWaitForReply({ type: 'copy_text_request', text });
await sendAndWaitForReply(event.windowContext, { type: 'copy_text_request', text });
},
},
toast: {
async show(args) {
await sendAndWaitForReply({ type: 'show_toast_request', ...args });
await sendAndWaitForReply(event.windowContext, { type: 'show_toast_request', ...args });
},
},
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>(payload);
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>(payload);
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>(payload);
const { httpResponse } = await sendAndWaitForReply<SendHttpRequestResponse>(
event.windowContext,
payload,
);
return httpResponse;
},
async render(args) {
const payload = { type: 'render_http_request_request', ...args } as const;
const result = await sendAndWaitForReply<RenderHttpRequestResponse>(payload);
return result.httpRequest;
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;
},
},
});
// Message comes into the plugin to be processed
parentPort!.on('message', async ({ payload, id: replyId }: InternalEvent) => {
parentPort!.on('message', async (event: InternalEvent) => {
const { windowContext, payload, id: replyId } = event;
const ctx = newCtx(event);
try {
if (payload.type === 'boot_request') {
const payload: InternalEventPayload = {
@@ -152,7 +211,7 @@ async function initialize() {
version: pkg.version,
capabilities,
};
sendPayload(payload, replyId);
sendPayload(windowContext, payload, replyId);
return;
}
@@ -160,7 +219,7 @@ async function initialize() {
const payload: InternalEventPayload = {
type: 'terminate_response',
};
sendPayload(payload, replyId);
sendPayload(windowContext, payload, replyId);
return;
}
@@ -171,7 +230,7 @@ async function initialize() {
type: 'import_response',
resources: reply?.resources,
};
sendPayload(replyPayload, replyId);
sendPayload(windowContext, replyPayload, replyId);
return;
} else {
// Continue, to send back an empty reply
@@ -187,7 +246,7 @@ async function initialize() {
type: 'export_http_request_response',
content: reply,
};
sendPayload(replyPayload, replyId);
sendPayload(windowContext, replyPayload, replyId);
return;
}
@@ -200,7 +259,7 @@ async function initialize() {
type: 'filter_response',
content: reply,
};
sendPayload(replyPayload, replyId);
sendPayload(windowContext, replyPayload, replyId);
return;
}
@@ -220,7 +279,7 @@ async function initialize() {
pluginRefId,
actions: reply,
};
sendPayload(replyPayload, replyId);
sendPayload(windowContext, replyPayload, replyId);
return;
}
@@ -240,7 +299,7 @@ async function initialize() {
pluginRefId,
functions: reply,
};
sendPayload(replyPayload, replyId);
sendPayload(windowContext, replyPayload, replyId);
return;
}
@@ -253,7 +312,7 @@ async function initialize() {
);
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
sendEmpty(replyId);
sendEmpty(windowContext, replyId);
return;
}
}
@@ -267,7 +326,14 @@ async function initialize() {
);
if (typeof action?.onRender === 'function') {
const result = await action.onRender(ctx, payload.args);
sendPayload({ type: 'call_template_function_response', value: result ?? null }, replyId);
sendPayload(
windowContext,
{
type: 'call_template_function_response',
value: result ?? null,
},
replyId,
);
return;
}
}
@@ -281,7 +347,7 @@ async function initialize() {
}
// No matches, so send back an empty response so the caller doesn't block forever
sendEmpty(replyId);
sendEmpty(windowContext, replyId);
});
}

View File

@@ -1,89 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Environment } from "./models";
import type { Folder } from "./models";
import type { GrpcRequest } from "./models";
import type { HttpRequest } from "./models";
import type { HttpResponse } from "./models";
import type { Workspace } from "./models";
export type BootRequest = { dir: string, };
export type BootResponse = { name: string, version: string, capabilities: Array<string>, };
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, };
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
export type CallTemplateFunctionResponse = { value: string | null, };
export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
export type CopyTextRequest = { text: string, };
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
export type ExportHttpRequestResponse = { content: string, };
export type FilterRequest = { content: string, filter: string, };
export type FilterResponse = { content: string, };
export type FindHttpResponsesRequest = { requestId: string, limit: number | null, };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
export type GetHttpRequestActionsRequest = Record<string, never>;
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
export type GetHttpRequestByIdRequest = { id: string, };
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
export type HttpRequestAction = { key: string, label: string, icon: string | null, };
export type Icon = "copy" | "info" | "check_circle" | "alert_triangle";
export type ImportRequest = { content: string, };
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "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" } & GetHttpRequestActionsRequest | { "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": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "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" };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowToastRequest = { message: string, color?: Color | null, icon?: Icon | null, };
export type TemplateFunction = { name: string, args: Array<TemplateFunctionArg>, };
export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg;
export type TemplateFunctionBaseArg = { name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
export type TemplateFunctionCheckboxArg = { name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
export type TemplateFunctionHttpRequestArg = { name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
export type TemplateFunctionSelectArg = { options: Array<TemplateFunctionSelectOption>, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
export type TemplateFunctionSelectOption = { name: string, value: string, };
export type TemplateFunctionTextArg = { placeholder?: string | null, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,7 @@
edition = "2018"
# Widths
chain_width = 100
max_width = 100
single_line_if_else_max_width = 100
fn_call_width = 100

View File

@@ -4,7 +4,7 @@ const Downloader = require('nodejs-file-downloader');
const { rmSync, cpSync, mkdirSync, existsSync } = require('node:fs');
const { execSync } = require('node:child_process');
const NODE_VERSION = 'v22.5.1';
const NODE_VERSION = 'v22.9.0';
// `${process.platform}_${process.arch}`
const MAC_ARM = 'darwin_arm64';
@@ -53,7 +53,12 @@ rmSync(tmpDir, { recursive: true, force: true });
(async function () {
// Download GitHub release artifact
const { filePath } = await new Downloader({ url, directory: tmpDir }).download();
console.log('Downloading NodeJS at', url);
const { filePath } = await new Downloader({
url,
directory: tmpDir,
timeout: 1000 * 60 * 2,
}).download();
// Decompress to the same directory
await decompress(filePath, tmpDir, {});

View File

@@ -3,8 +3,8 @@ const path = require('node:path');
const { execSync } = require('node:child_process');
const pluginsDir = process.env.YAAK_PLUGINS_DIR;
if (!pluginsDir) {
console.log('YAAK_PLUGINS_DIR is not set');
process.exit(1);
console.log('Skipping bundled plugins build because YAAK_PLUGINS_DIR is not set');
return;
}
console.log('Installing Yaak plugins dependencies', pluginsDir);

View File

@@ -1,10 +1,10 @@
const decompress = require('decompress');
const Downloader = require('nodejs-file-downloader');
const path = require('node:path');
const { rmSync, mkdirSync, cpSync, existsSync } = require('node:fs');
const { rmSync, mkdirSync, cpSync, existsSync, statSync, chmodSync } = require('node:fs');
const { execSync } = require('node:child_process');
const VERSION = '27.2';
const VERSION = '28.3';
// `${process.platform}_${process.arch}`
const MAC_ARM = 'darwin_arm64';
@@ -67,6 +67,11 @@ mkdirSync(dstDir, { recursive: true });
cpSync(includeSrc, includeDst, { recursive: true });
rmSync(tmpDir, { recursive: true, force: true });
// Make binary writable, so we can sign it during release
const stat = statSync(binDst);
const newMode = stat.mode | 0o200;
chmodSync(binDst, newMode);
console.log('Downloaded protoc to', binDst);
})().catch((err) => console.log('Script failed:', err));

View File

@@ -2,4 +2,5 @@
# will have compiled files and executables
target/
vendored
vendored/*
!vendored/plugins

1617
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,20 @@
[workspace]
members = ["yaak_grpc", "yaak_templates", "yaak_plugin_runtime", "yaak_models"]
members = [
"yaak-grpc",
"yaak-license",
"yaak-models",
"yaak-plugins",
"yaak-sse",
"yaak-sync",
"yaak-templates",
]
[package]
name = "yaak-app"
version = "0.0.0"
edition = "2021"
authors = ["Gregory Schier"]
publish = false
# Produce a library for mobile support
[lib]
@@ -15,8 +24,11 @@ crate-type = ["staticlib", "cdylib", "lib"]
[profile.release]
strip = true # Automatically strip symbols from the binary.
[features]
cargo-clippy = []
[build-dependencies]
tauri-build = { version = "2.0.0-rc.12", features = [] }
tauri-build = { version = "2.0.4", features = [] }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"
@@ -26,42 +38,54 @@ cocoa = "0.26.0"
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
[dependencies]
yaak_grpc = { path = "yaak_grpc" }
yaak_templates = { path = "yaak_templates" }
yaak_plugin_runtime = { workspace = true }
yaak_models = { workspace = true }
anyhow = "1.0.86"
base64 = "0.22.0"
chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2"
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.13.0" }
hex_color = "3.0.0"
http = "1"
log = "0.4.21"
rand = "0.8.5"
regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider"] }
reqwest_cookie_store = "0.8.0"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
serde_yaml = "0.9.34"
tauri = { workspace = true }
rustls = { version = "0.23.21", default-features = false }
rustls-platform-verifier = "0.5.0"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
tauri-plugin-clipboard-manager = "2.2.0"
tauri-plugin-dialog = "2.2.0"
tauri-plugin-fs = "2.2.0"
tauri-plugin-log = { version = "2.2.0", features = ["colored"] }
tauri-plugin-opener = "2.2.2"
tauri-plugin-os = "2.2.0"
tauri-plugin-shell = { workspace = true }
tauri-plugin-clipboard-manager = "2.1.0-beta.7"
tauri-plugin-dialog = "2.0.0-rc.7"
tauri-plugin-fs = "2.0.0-rc.5"
tauri-plugin-log = { version = "2.0.0-rc.2", features = ["colored"] }
tauri-plugin-os = "2.0.0-rc.1"
tauri-plugin-updater = "2.0.0-rc.3"
tauri-plugin-window-state = "2.0.0-rc.5"
tauri-plugin-single-instance = "2.2.0"
tauri-plugin-updater = "2.3.0"
tauri-plugin-window-state = "2.2.0"
tokio = { version = "1.36.0", features = ["sync"] }
tokio-stream = "0.1.15"
uuid = "1.7.0"
thiserror = "1.0.61"
ts-rs = { workspace = true }
mime_guess = "2.0.5"
urlencoding = "2.1.3"
uuid = "1.7.0"
yaak-grpc = { path = "yaak-grpc" }
yaak-license = { path = "yaak-license" }
yaak-models = { workspace = true }
yaak-plugins = { workspace = true }
yaak-sse = { workspace = true }
yaak-sync = { path = "yaak-sync" }
yaak-templates = { path = "yaak-templates" }
[workspace.dependencies]
yaak_models = { path = "yaak_models" }
yaak_plugin_runtime = { path = "yaak_plugin_runtime" }
tauri-plugin-shell = "2.0.0-rc.3"
tauri = { version = "2.0.0-rc.15", features = ["devtools", "protocol-asset"] }
yaak-models = { path = "yaak-models" }
yaak-sse = { path = "yaak-sse" }
yaak-plugins = { path = "yaak-plugins" }
serde = "1.0.215"
serde_json = "1.0.132"
tauri-plugin-shell = "2.2.0"
tauri = "2.2.0"
thiserror = "2.0.3"
ts-rs = "10.0.0"
reqwest = "0.12.12"

View File

@@ -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 AnalyticsAction = "cancel" | "click" | "commit" | "create" | "delete" | "delete_many" | "duplicate" | "export" | "hide" | "import" | "launch" | "launch_first" | "launch_update" | "send" | "show" | "toggle" | "update" | "upsert";
export type AnalyticsResource = "app" | "appearance" | "button" | "checkbox" | "cookie_jar" | "dialog" | "environment" | "folder" | "grpc_connection" | "grpc_event" | "grpc_request" | "http_request" | "http_response" | "link" | "key_value" | "plugin" | "select" | "setting" | "sidebar" | "tab" | "theme" | "workspace";

View File

@@ -16,6 +16,7 @@
"clipboard-manager:allow-read-text",
"dialog:allow-open",
"dialog:allow-save",
"fs:allow-read-dir",
"fs:allow-read-file",
"fs:allow-read-text-file",
{
@@ -29,9 +30,13 @@
}
]
},
"shell:allow-open",
"opener:allow-open-url",
"opener:allow-open-path",
"opener:allow-default-urls",
"opener:allow-reveal-item-in-dir",
"core:webview:allow-set-webview-zoom",
"core:window:allow-close",
"core:window:allow-internal-toggle-maximize",
"core:window:allow-is-fullscreen",
"core:window:allow-maximize",
"core:window:allow-minimize",
@@ -43,6 +48,8 @@
"core:window:allow-toggle-maximize",
"core:window:allow-unmaximize",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text"
"clipboard-manager:allow-write-text",
"yaak-license:default",
"yaak-sync:default"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","opener:allow-reveal-item-in-dir","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}

View File

@@ -37,7 +37,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": [
"identifier",
@@ -84,7 +84,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"
@@ -140,7 +140,7 @@
"identifier": {
"anyOf": [
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -984,6 +984,11 @@
"type": "string",
"const": "fs:allow-seek"
},
{
"description": "Enables the size command without any pre-configured scope.",
"type": "string",
"const": "fs:allow-size"
},
{
"description": "Enables the stat command without any pre-configured scope.",
"type": "string",
@@ -1109,6 +1114,11 @@
"type": "string",
"const": "fs:deny-seek"
},
{
"description": "Denies the size command without any pre-configured scope.",
"type": "string",
"const": "fs:deny-size"
},
{
"description": "Denies the stat command without any pre-configured scope.",
"type": "string",
@@ -1581,7 +1591,7 @@
"description": "FS scope entry.",
"anyOf": [
{
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
{
@@ -1591,7 +1601,7 @@
],
"properties": {
"path": {
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
@@ -1605,7 +1615,7 @@
"description": "FS scope entry.",
"anyOf": [
{
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
{
@@ -1615,7 +1625,167 @@
],
"properties": {
"path": {
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
}
]
}
}
}
},
"properties": {
"identifier": {
"description": "Identifier of the permission or permission set.",
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
]
}
}
},
{
"if": {
"properties": {
"identifier": {
"anyOf": [
{
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer",
"type": "string",
"const": "opener:default"
},
{
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
"type": "string",
"const": "opener:allow-default-urls"
},
{
"description": "Enables the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-path"
},
{
"description": "Enables the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-url"
},
{
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-reveal-item-in-dir"
},
{
"description": "Denies the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-path"
},
{
"description": "Denies the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-url"
},
{
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-reveal-item-in-dir"
}
]
}
}
},
"then": {
"properties": {
"allow": {
"items": {
"title": "OpenerScopeEntry",
"description": "Opener scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"url"
],
"properties": {
"app": {
"description": "An application to open this url with, for example: firefox.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"url": {
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
},
{
"type": "object",
"required": [
"path"
],
"properties": {
"app": {
"description": "An application to open this path with, for example: xdg-open.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"path": {
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
}
]
}
},
"deny": {
"items": {
"title": "OpenerScopeEntry",
"description": "Opener scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"url"
],
"properties": {
"app": {
"description": "An application to open this url with, for example: firefox.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"url": {
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
},
{
"type": "object",
"required": [
"path"
],
"properties": {
"app": {
"description": "An application to open this path with, for example: xdg-open.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"path": {
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
@@ -1704,72 +1874,122 @@
"properties": {
"allow": {
"items": {
"title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
"title": "ShellScopeEntry",
"description": "Shell scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"cmd",
"name"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
}
]
},
"additionalProperties": false
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
{
"type": "object",
"required": [
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
}
},
"additionalProperties": false
}
}
]
}
},
"deny": {
"items": {
"title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
"title": "ShellScopeEntry",
"description": "Shell scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"cmd",
"name"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
}
]
},
"additionalProperties": false
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
{
"type": "object",
"required": [
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
}
},
"additionalProperties": false
}
}
]
}
}
}
@@ -1922,6 +2142,11 @@
"type": "string",
"const": "core:app:allow-name"
},
{
"description": "Enables the set_app_theme command without any pre-configured scope.",
"type": "string",
"const": "core:app:allow-set-app-theme"
},
{
"description": "Enables the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -1952,6 +2177,11 @@
"type": "string",
"const": "core:app:deny-name"
},
{
"description": "Denies the set_app_theme command without any pre-configured scope.",
"type": "string",
"const": "core:app:deny-set-app-theme"
},
{
"description": "Denies the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -2507,6 +2737,11 @@
"type": "string",
"const": "core:webview:default"
},
{
"description": "Enables the clear_all_browsing_data command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-clear-all-browsing-data"
},
{
"description": "Enables the create_webview command without any pre-configured scope.",
"type": "string",
@@ -2537,6 +2772,11 @@
"type": "string",
"const": "core:webview:allow-reparent"
},
{
"description": "Enables the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-set-webview-background-color"
},
{
"description": "Enables the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2562,16 +2802,31 @@
"type": "string",
"const": "core:webview:allow-webview-close"
},
{
"description": "Enables the webview_hide command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-hide"
},
{
"description": "Enables the webview_position command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-position"
},
{
"description": "Enables the webview_show command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-show"
},
{
"description": "Enables the webview_size command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-size"
},
{
"description": "Denies the clear_all_browsing_data command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-clear-all-browsing-data"
},
{
"description": "Denies the create_webview command without any pre-configured scope.",
"type": "string",
@@ -2602,6 +2857,11 @@
"type": "string",
"const": "core:webview:deny-reparent"
},
{
"description": "Denies the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-set-webview-background-color"
},
{
"description": "Denies the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2627,11 +2887,21 @@
"type": "string",
"const": "core:webview:deny-webview-close"
},
{
"description": "Denies the webview_hide command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-hide"
},
{
"description": "Denies the webview_position command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-position"
},
{
"description": "Denies the webview_show command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-show"
},
{
"description": "Denies the webview_size command without any pre-configured scope.",
"type": "string",
@@ -2712,6 +2982,11 @@
"type": "string",
"const": "core:window:allow-is-decorated"
},
{
"description": "Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-is-enabled"
},
{
"description": "Enables the is_focused command without any pre-configured scope.",
"type": "string",
@@ -2802,6 +3077,21 @@
"type": "string",
"const": "core:window:allow-set-always-on-top"
},
{
"description": "Enables the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-background-color"
},
{
"description": "Enables the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-count"
},
{
"description": "Enables the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-label"
},
{
"description": "Enables the set_closable command without any pre-configured scope.",
"type": "string",
@@ -2842,6 +3132,11 @@
"type": "string",
"const": "core:window:allow-set-effects"
},
{
"description": "Enables the set_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-enabled"
},
{
"description": "Enables the set_focus command without any pre-configured scope.",
"type": "string",
@@ -2882,6 +3177,11 @@
"type": "string",
"const": "core:window:allow-set-minimizable"
},
{
"description": "Enables the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-overlay-icon"
},
{
"description": "Enables the set_position command without any pre-configured scope.",
"type": "string",
@@ -2917,6 +3217,11 @@
"type": "string",
"const": "core:window:allow-set-skip-taskbar"
},
{
"description": "Enables the set_theme command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-theme"
},
{
"description": "Enables the set_title command without any pre-configured scope.",
"type": "string",
@@ -3042,6 +3347,11 @@
"type": "string",
"const": "core:window:deny-is-decorated"
},
{
"description": "Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-is-enabled"
},
{
"description": "Denies the is_focused command without any pre-configured scope.",
"type": "string",
@@ -3132,6 +3442,21 @@
"type": "string",
"const": "core:window:deny-set-always-on-top"
},
{
"description": "Denies the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-background-color"
},
{
"description": "Denies the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-count"
},
{
"description": "Denies the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-label"
},
{
"description": "Denies the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3172,6 +3497,11 @@
"type": "string",
"const": "core:window:deny-set-effects"
},
{
"description": "Denies the set_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-enabled"
},
{
"description": "Denies the set_focus command without any pre-configured scope.",
"type": "string",
@@ -3212,6 +3542,11 @@
"type": "string",
"const": "core:window:deny-set-minimizable"
},
{
"description": "Denies the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-overlay-icon"
},
{
"description": "Denies the set_position command without any pre-configured scope.",
"type": "string",
@@ -3247,6 +3582,11 @@
"type": "string",
"const": "core:window:deny-set-skip-taskbar"
},
{
"description": "Denies the set_theme command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-theme"
},
{
"description": "Denies the set_title command without any pre-configured scope.",
"type": "string",
@@ -3358,7 +3698,7 @@
"const": "dialog:deny-save"
},
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -4202,6 +4542,11 @@
"type": "string",
"const": "fs:allow-seek"
},
{
"description": "Enables the size command without any pre-configured scope.",
"type": "string",
"const": "fs:allow-size"
},
{
"description": "Enables the stat command without any pre-configured scope.",
"type": "string",
@@ -4327,6 +4672,11 @@
"type": "string",
"const": "fs:deny-seek"
},
{
"description": "Denies the size command without any pre-configured scope.",
"type": "string",
"const": "fs:deny-size"
},
{
"description": "Denies the stat command without any pre-configured scope.",
"type": "string",
@@ -4802,6 +5152,46 @@
"type": "string",
"const": "log:deny-log"
},
{
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer",
"type": "string",
"const": "opener:default"
},
{
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
"type": "string",
"const": "opener:allow-default-urls"
},
{
"description": "Enables the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-path"
},
{
"description": "Enables the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-url"
},
{
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-reveal-item-in-dir"
},
{
"description": "Denies the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-path"
},
{
"description": "Denies the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-url"
},
{
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-reveal-item-in-dir"
},
{
"description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n",
"type": "string",
@@ -5021,6 +5411,76 @@
"description": "Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"const": "window-state:deny-save-window-state"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-license:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-activate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-check"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-activate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-check"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-sync:default"
},
{
"description": "Enables the apply command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-apply"
},
{
"description": "Enables the calculate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-calculate"
},
{
"description": "Enables the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-calculate-fs"
},
{
"description": "Enables the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-watch"
},
{
"description": "Denies the apply command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-apply"
},
{
"description": "Denies the calculate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-calculate"
},
{
"description": "Denies the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-calculate-fs"
},
{
"description": "Denies the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-watch"
}
]
},
@@ -5118,7 +5578,24 @@
}
]
},
"ShellAllowedArg": {
"Application": {
"description": "Opener scope application.",
"anyOf": [
{
"description": "Open in default application.",
"type": "null"
},
{
"description": "If true, allow open with any application.",
"type": "boolean"
},
{
"description": "Allow specific application to open with.",
"type": "string"
}
]
},
"ShellScopeEntryAllowedArg": {
"description": "A command argument allowed to be executed by the webview API.",
"anyOf": [
{
@@ -5146,18 +5623,18 @@
}
]
},
"ShellAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"ShellScopeEntryAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.",
"type": "array",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
"$ref": "#/definitions/ShellScopeEntryAllowedArg"
}
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
],
"definitions": {
"Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```",
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object",
"required": [
"identifier",
@@ -84,7 +84,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"
@@ -140,7 +140,7 @@
"identifier": {
"anyOf": [
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -984,6 +984,11 @@
"type": "string",
"const": "fs:allow-seek"
},
{
"description": "Enables the size command without any pre-configured scope.",
"type": "string",
"const": "fs:allow-size"
},
{
"description": "Enables the stat command without any pre-configured scope.",
"type": "string",
@@ -1109,6 +1114,11 @@
"type": "string",
"const": "fs:deny-seek"
},
{
"description": "Denies the size command without any pre-configured scope.",
"type": "string",
"const": "fs:deny-size"
},
{
"description": "Denies the stat command without any pre-configured scope.",
"type": "string",
@@ -1581,7 +1591,7 @@
"description": "FS scope entry.",
"anyOf": [
{
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
{
@@ -1591,7 +1601,7 @@
],
"properties": {
"path": {
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
@@ -1605,7 +1615,7 @@
"description": "FS scope entry.",
"anyOf": [
{
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
{
@@ -1615,7 +1625,167 @@
],
"properties": {
"path": {
"description": "FS scope path.",
"description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
}
]
}
}
}
},
"properties": {
"identifier": {
"description": "Identifier of the permission or permission set.",
"allOf": [
{
"$ref": "#/definitions/Identifier"
}
]
}
}
},
{
"if": {
"properties": {
"identifier": {
"anyOf": [
{
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer",
"type": "string",
"const": "opener:default"
},
{
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
"type": "string",
"const": "opener:allow-default-urls"
},
{
"description": "Enables the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-path"
},
{
"description": "Enables the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-url"
},
{
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-reveal-item-in-dir"
},
{
"description": "Denies the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-path"
},
{
"description": "Denies the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-url"
},
{
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-reveal-item-in-dir"
}
]
}
}
},
"then": {
"properties": {
"allow": {
"items": {
"title": "OpenerScopeEntry",
"description": "Opener scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"url"
],
"properties": {
"app": {
"description": "An application to open this url with, for example: firefox.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"url": {
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
},
{
"type": "object",
"required": [
"path"
],
"properties": {
"app": {
"description": "An application to open this path with, for example: xdg-open.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"path": {
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
}
]
}
},
"deny": {
"items": {
"title": "OpenerScopeEntry",
"description": "Opener scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"url"
],
"properties": {
"app": {
"description": "An application to open this url with, for example: firefox.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"url": {
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
"type": "string"
}
}
},
{
"type": "object",
"required": [
"path"
],
"properties": {
"app": {
"description": "An application to open this path with, for example: xdg-open.",
"allOf": [
{
"$ref": "#/definitions/Application"
}
]
},
"path": {
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
}
}
@@ -1704,72 +1874,122 @@
"properties": {
"allow": {
"items": {
"title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
"title": "ShellScopeEntry",
"description": "Shell scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"cmd",
"name"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
}
]
},
"additionalProperties": false
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
{
"type": "object",
"required": [
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
}
},
"additionalProperties": false
}
}
]
}
},
"deny": {
"items": {
"title": "Entry",
"description": "A command allowed to be executed by the webview API.",
"type": "object",
"required": [
"args",
"cmd",
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellAllowedArgs"
"title": "ShellScopeEntry",
"description": "Shell scope entry.",
"anyOf": [
{
"type": "object",
"required": [
"cmd",
"name"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
}
]
},
"additionalProperties": false
},
"cmd": {
"description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
"type": "string"
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
{
"type": "object",
"required": [
"name",
"sidecar"
],
"properties": {
"args": {
"description": "The allowed arguments for the command execution.",
"allOf": [
{
"$ref": "#/definitions/ShellScopeEntryAllowedArgs"
}
]
},
"name": {
"description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.",
"type": "string"
},
"sidecar": {
"description": "If this command is a sidecar command.",
"type": "boolean"
}
},
"additionalProperties": false
}
}
]
}
}
}
@@ -1922,6 +2142,11 @@
"type": "string",
"const": "core:app:allow-name"
},
{
"description": "Enables the set_app_theme command without any pre-configured scope.",
"type": "string",
"const": "core:app:allow-set-app-theme"
},
{
"description": "Enables the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -1952,6 +2177,11 @@
"type": "string",
"const": "core:app:deny-name"
},
{
"description": "Denies the set_app_theme command without any pre-configured scope.",
"type": "string",
"const": "core:app:deny-set-app-theme"
},
{
"description": "Denies the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -2507,6 +2737,11 @@
"type": "string",
"const": "core:webview:default"
},
{
"description": "Enables the clear_all_browsing_data command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-clear-all-browsing-data"
},
{
"description": "Enables the create_webview command without any pre-configured scope.",
"type": "string",
@@ -2537,6 +2772,11 @@
"type": "string",
"const": "core:webview:allow-reparent"
},
{
"description": "Enables the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-set-webview-background-color"
},
{
"description": "Enables the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2562,16 +2802,31 @@
"type": "string",
"const": "core:webview:allow-webview-close"
},
{
"description": "Enables the webview_hide command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-hide"
},
{
"description": "Enables the webview_position command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-position"
},
{
"description": "Enables the webview_show command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-show"
},
{
"description": "Enables the webview_size command without any pre-configured scope.",
"type": "string",
"const": "core:webview:allow-webview-size"
},
{
"description": "Denies the clear_all_browsing_data command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-clear-all-browsing-data"
},
{
"description": "Denies the create_webview command without any pre-configured scope.",
"type": "string",
@@ -2602,6 +2857,11 @@
"type": "string",
"const": "core:webview:deny-reparent"
},
{
"description": "Denies the set_webview_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-set-webview-background-color"
},
{
"description": "Denies the set_webview_focus command without any pre-configured scope.",
"type": "string",
@@ -2627,11 +2887,21 @@
"type": "string",
"const": "core:webview:deny-webview-close"
},
{
"description": "Denies the webview_hide command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-hide"
},
{
"description": "Denies the webview_position command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-position"
},
{
"description": "Denies the webview_show command without any pre-configured scope.",
"type": "string",
"const": "core:webview:deny-webview-show"
},
{
"description": "Denies the webview_size command without any pre-configured scope.",
"type": "string",
@@ -2712,6 +2982,11 @@
"type": "string",
"const": "core:window:allow-is-decorated"
},
{
"description": "Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-is-enabled"
},
{
"description": "Enables the is_focused command without any pre-configured scope.",
"type": "string",
@@ -2802,6 +3077,21 @@
"type": "string",
"const": "core:window:allow-set-always-on-top"
},
{
"description": "Enables the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-background-color"
},
{
"description": "Enables the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-count"
},
{
"description": "Enables the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-badge-label"
},
{
"description": "Enables the set_closable command without any pre-configured scope.",
"type": "string",
@@ -2842,6 +3132,11 @@
"type": "string",
"const": "core:window:allow-set-effects"
},
{
"description": "Enables the set_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-enabled"
},
{
"description": "Enables the set_focus command without any pre-configured scope.",
"type": "string",
@@ -2882,6 +3177,11 @@
"type": "string",
"const": "core:window:allow-set-minimizable"
},
{
"description": "Enables the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-overlay-icon"
},
{
"description": "Enables the set_position command without any pre-configured scope.",
"type": "string",
@@ -2917,6 +3217,11 @@
"type": "string",
"const": "core:window:allow-set-skip-taskbar"
},
{
"description": "Enables the set_theme command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-set-theme"
},
{
"description": "Enables the set_title command without any pre-configured scope.",
"type": "string",
@@ -3042,6 +3347,11 @@
"type": "string",
"const": "core:window:deny-is-decorated"
},
{
"description": "Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-is-enabled"
},
{
"description": "Denies the is_focused command without any pre-configured scope.",
"type": "string",
@@ -3132,6 +3442,21 @@
"type": "string",
"const": "core:window:deny-set-always-on-top"
},
{
"description": "Denies the set_background_color command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-background-color"
},
{
"description": "Denies the set_badge_count command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-count"
},
{
"description": "Denies the set_badge_label command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-badge-label"
},
{
"description": "Denies the set_closable command without any pre-configured scope.",
"type": "string",
@@ -3172,6 +3497,11 @@
"type": "string",
"const": "core:window:deny-set-effects"
},
{
"description": "Denies the set_enabled command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-enabled"
},
{
"description": "Denies the set_focus command without any pre-configured scope.",
"type": "string",
@@ -3212,6 +3542,11 @@
"type": "string",
"const": "core:window:deny-set-minimizable"
},
{
"description": "Denies the set_overlay_icon command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-overlay-icon"
},
{
"description": "Denies the set_position command without any pre-configured scope.",
"type": "string",
@@ -3247,6 +3582,11 @@
"type": "string",
"const": "core:window:deny-set-skip-taskbar"
},
{
"description": "Denies the set_theme command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-set-theme"
},
{
"description": "Denies the set_title command without any pre-configured scope.",
"type": "string",
@@ -3358,7 +3698,7 @@
"const": "dialog:deny-save"
},
{
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
"type": "string",
"const": "fs:default"
},
@@ -4202,6 +4542,11 @@
"type": "string",
"const": "fs:allow-seek"
},
{
"description": "Enables the size command without any pre-configured scope.",
"type": "string",
"const": "fs:allow-size"
},
{
"description": "Enables the stat command without any pre-configured scope.",
"type": "string",
@@ -4327,6 +4672,11 @@
"type": "string",
"const": "fs:deny-seek"
},
{
"description": "Denies the size command without any pre-configured scope.",
"type": "string",
"const": "fs:deny-size"
},
{
"description": "Denies the stat command without any pre-configured scope.",
"type": "string",
@@ -4802,6 +5152,46 @@
"type": "string",
"const": "log:deny-log"
},
{
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer",
"type": "string",
"const": "opener:default"
},
{
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
"type": "string",
"const": "opener:allow-default-urls"
},
{
"description": "Enables the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-path"
},
{
"description": "Enables the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-open-url"
},
{
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:allow-reveal-item-in-dir"
},
{
"description": "Denies the open_path command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-path"
},
{
"description": "Denies the open_url command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-open-url"
},
{
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
"type": "string",
"const": "opener:deny-reveal-item-in-dir"
},
{
"description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n",
"type": "string",
@@ -5021,6 +5411,76 @@
"description": "Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"const": "window-state:deny-save-window-state"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-license:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-activate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-check"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-activate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-check"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-sync:default"
},
{
"description": "Enables the apply command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-apply"
},
{
"description": "Enables the calculate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-calculate"
},
{
"description": "Enables the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-calculate-fs"
},
{
"description": "Enables the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:allow-watch"
},
{
"description": "Denies the apply command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-apply"
},
{
"description": "Denies the calculate command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-calculate"
},
{
"description": "Denies the calculate_fs command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-calculate-fs"
},
{
"description": "Denies the watch command without any pre-configured scope.",
"type": "string",
"const": "yaak-sync:deny-watch"
}
]
},
@@ -5118,7 +5578,24 @@
}
]
},
"ShellAllowedArg": {
"Application": {
"description": "Opener scope application.",
"anyOf": [
{
"description": "Open in default application.",
"type": "null"
},
{
"description": "If true, allow open with any application.",
"type": "boolean"
},
{
"description": "Allow specific application to open with.",
"type": "string"
}
]
},
"ShellScopeEntryAllowedArg": {
"description": "A command argument allowed to be executed by the webview API.",
"anyOf": [
{
@@ -5146,18 +5623,18 @@
}
]
},
"ShellAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"ShellScopeEntryAllowedArgs": {
"description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.",
"anyOf": [
{
"description": "Use a simple boolean to allow all or disable all arguments to this command configuration.",
"type": "boolean"
},
{
"description": "A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.",
"description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.",
"type": "array",
"items": {
"$ref": "#/definitions/ShellAllowedArg"
"$ref": "#/definitions/ShellScopeEntryAllowedArg"
}
}
]

View File

@@ -0,0 +1,5 @@
ALTER TABLE http_responses
ADD COLUMN state TEXT DEFAULT 'closed' NOT NULL;
ALTER TABLE grpc_connections
ADD COLUMN state TEXT DEFAULT 'closed' NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN proxy TEXT;

View File

@@ -0,0 +1,8 @@
ALTER TABLE http_requests
ADD COLUMN description TEXT DEFAULT '' NOT NULL;
ALTER TABLE grpc_requests
ADD COLUMN description TEXT DEFAULT '' NOT NULL;
ALTER TABLE folders
ADD COLUMN description TEXT DEFAULT '' NOT NULL;

View File

@@ -0,0 +1,45 @@
-- Add the new field
ALTER TABLE environments
ADD COLUMN environment_id TEXT REFERENCES environments (id) ON DELETE CASCADE;
-- Create temporary column so we know which rows are meant to be base environments. We'll use this to update
-- child environments to point to them.
ALTER TABLE environments
ADD COLUMN migrated_base_env BOOLEAN DEFAULT FALSE NOT NULL;
-- Create a base environment for each workspace
INSERT INTO environments (id, workspace_id, name, variables, migrated_base_env)
SELECT (
-- This is the best way to generate a random string in SQLite, apparently
'ev_' || SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) ||
SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1)
),
workspaces.id,
'Global Variables',
variables,
TRUE
FROM workspaces;
-- Update all non-base environments to point to newly created base environments
UPDATE environments
SET environment_id = ( SELECT base_env.id
FROM environments AS base_env
WHERE base_env.workspace_id = environments.workspace_id
AND base_env.migrated_base_env IS TRUE )
WHERE migrated_base_env IS FALSE;
-- Drop temporary column
ALTER TABLE environments
DROP COLUMN migrated_base_env;
-- Drop the old variables column
-- IMPORTANT: Skip to give the user the option to roll back to a previous app version. We can drop it once the migration working in the real world
-- ALTER TABLE workspaces DROP COLUMN variables;

View File

@@ -0,0 +1,21 @@
ALTER TABLE workspaces
ADD COLUMN setting_sync_dir TEXT;
CREATE TABLE sync_states
(
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'sync_state' NOT NULL,
workspace_id TEXT NOT NULL
REFERENCES workspaces
ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
flushed_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
checksum TEXT NOT NULL,
model_id TEXT NOT NULL,
sync_dir TEXT NOT NULL,
rel_path TEXT NOT NULL,
UNIQUE (workspace_id, model_id)
);

View File

@@ -0,0 +1,2 @@
ALTER TABLE settings
ADD COLUMN editor_keymap TEXT DEFAULT 'codemirror' NOT NULL;

View File

@@ -0,0 +1,11 @@
CREATE TABLE workspace_metas
(
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'workspace_meta' NOT NULL,
workspace_id TEXT NOT NULL
REFERENCES workspaces ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
setting_sync_dir TEXT
);

View File

@@ -4,8 +4,11 @@ use log::{debug, info};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::{Manager, Runtime, WebviewWindow};
use yaak_models::queries::{generate_id, get_key_value_int, get_key_value_string, get_or_create_settings, set_key_value_int, set_key_value_string};
use ts_rs::TS;
use yaak_models::queries::{
generate_id, get_key_value_int, get_key_value_string, get_or_create_settings,
set_key_value_int, set_key_value_string, UpdateSource,
};
use crate::is_dev;
@@ -13,11 +16,14 @@ const NAMESPACE: &str = "analytics";
const NUM_LAUNCHES_KEY: &str = "num_launches";
// serializable
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "analytics.ts")]
pub enum AnalyticsResource {
App,
Appearance,
Button,
Checkbox,
CookieJar,
Dialog,
Environment,
@@ -27,10 +33,13 @@ pub enum AnalyticsResource {
GrpcRequest,
HttpRequest,
HttpResponse,
Link,
KeyValue,
Plugin,
Select,
Setting,
Sidebar,
Tab,
Theme,
Workspace,
}
@@ -43,18 +52,16 @@ impl AnalyticsResource {
impl Display for AnalyticsResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string(self).unwrap().replace("\"", "")
)
write!(f, "{}", serde_json::to_string(self).unwrap().replace("\"", ""))
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "analytics.ts")]
pub enum AnalyticsAction {
Cancel,
Click,
Commit,
Create,
Delete,
@@ -81,11 +88,7 @@ impl AnalyticsAction {
impl Display for AnalyticsAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string(self).unwrap().replace("\"", "")
)
write!(f, "{}", serde_json::to_string(self).unwrap().replace("\"", ""))
}
}
@@ -107,13 +110,7 @@ pub async fn track_launch_event<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEvent
info.current_version = w.package_info().version.to_string();
if info.previous_version.is_empty() {
track_event(
w,
AnalyticsResource::App,
AnalyticsAction::LaunchFirst,
None,
)
.await;
track_event(w, AnalyticsResource::App, AnalyticsAction::LaunchFirst, None).await;
} else {
info.launched_after_update = info.current_version != info.previous_version;
if info.launched_after_update {
@@ -143,9 +140,11 @@ pub async fn track_launch_event<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEvent
NAMESPACE,
last_tracked_version_key,
info.current_version.as_str(),
&UpdateSource::Background,
)
.await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches).await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, &UpdateSource::Background)
.await;
info
}
@@ -156,7 +155,6 @@ pub async fn track_event<R: Runtime>(
action: AnalyticsAction,
attributes: Option<Value>,
) {
let id = get_id(w).await;
let event = format!("{}.{}", resource, action);
let attributes_json = attributes.unwrap_or("{}".to_string().into()).to_string();
@@ -180,16 +178,13 @@ pub async fn track_event<R: Runtime>(
("tz", tz),
("xy", get_window_size(w)),
];
let req = reqwest::Client::builder()
.build()
.unwrap()
.get(format!("{base_url}/t/e"))
.query(&params);
let req =
reqwest::Client::builder().build().unwrap().get(format!("{base_url}/t/e")).query(&params);
let settings = get_or_create_settings(w).await;
if !settings.telemetry {
info!("Track event (disabled): {}", event);
return
return;
}
// Disable analytics actual sending in dev
@@ -226,18 +221,15 @@ fn get_window_size<R: Runtime>(w: &WebviewWindow<R>) -> String {
let width: f64 = size.width as f64 / scale_factor;
let height: f64 = size.height as f64 / scale_factor;
format!(
"{}x{}",
(width / 100.0).round() * 100.0,
(height / 100.0).round() * 100.0
)
format!("{}x{}", (width / 100.0).round() * 100.0, (height / 100.0).round() * 100.0)
}
async fn get_id<R: Runtime>(w: &WebviewWindow<R>) -> String {
let id = get_key_value_string(w, "analytics", "id", "").await;
if id.is_empty() {
let new_id = generate_id();
set_key_value_string(w, "analytics", "id", new_id.as_str()).await;
set_key_value_string(w, "analytics", "id", new_id.as_str(), &UpdateSource::Background)
.await;
new_id
} else {
id

View File

@@ -1,77 +0,0 @@
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use tauri::{Manager, WebviewWindow};
use yaak_models::models::{Environment, Folder, GrpcRequest, HttpRequest, Workspace};
#[derive(Default, Debug, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct WorkspaceExport {
pub yaak_version: String,
pub yaak_schema: i64,
pub timestamp: NaiveDateTime,
pub resources: WorkspaceExportResources,
}
#[derive(Default, Debug, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct WorkspaceExportResources {
pub workspaces: Vec<Workspace>,
pub environments: Vec<Environment>,
pub folders: Vec<Folder>,
pub http_requests: Vec<HttpRequest>,
pub grpc_requests: Vec<GrpcRequest>,
}
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct ImportResult {
pub resources: WorkspaceExportResources,
}
pub async fn get_workspace_export_resources(
window: &WebviewWindow,
workspace_ids: Vec<&str>,
) -> WorkspaceExport {
let app_handle = window.app_handle();
let mut data = WorkspaceExport {
yaak_version: app_handle.package_info().version.clone().to_string(),
yaak_schema: 2,
timestamp: chrono::Utc::now().naive_utc(),
resources: WorkspaceExportResources {
workspaces: Vec::new(),
environments: Vec::new(),
folders: Vec::new(),
http_requests: Vec::new(),
grpc_requests: Vec::new(),
},
};
for workspace_id in workspace_ids {
data.resources.workspaces.push(
yaak_models::queries::get_workspace(window, workspace_id)
.await
.expect("Failed to get workspace"),
);
data.resources.environments.append(
&mut yaak_models::queries::list_environments(window, workspace_id)
.await
.expect("Failed to get environments"),
);
data.resources.folders.append(
&mut yaak_models::queries::list_folders(window, workspace_id)
.await
.expect("Failed to get folders"),
);
data.resources.http_requests.append(
&mut yaak_models::queries::list_http_requests(window, workspace_id)
.await
.expect("Failed to get http requests"),
);
data.resources.grpc_requests.append(
&mut yaak_models::queries::list_grpc_requests(window, workspace_id)
.await
.expect("Failed to get grpc requests"),
);
}
return data;
}

View File

@@ -1,12 +1,3 @@
use std::collections::BTreeMap;
use std::fs;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use crate::render::render_http_request;
use crate::response_err;
use crate::template_callback::PluginTemplateCallback;
@@ -14,35 +5,60 @@ use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use http::header::{ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderName, HeaderValue};
use log::{error, warn};
use log::{debug, error, warn};
use mime_guess::Mime;
use reqwest::redirect::Policy;
use reqwest::Method;
use reqwest::{multipart, Url};
use reqwest::{multipart, Proxy, Url};
use reqwest::{Method, Response};
use rustls::ClientConfig;
use rustls_platform_verifier::ConfigVerifierExt;
use serde_json::Value;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tauri::{Manager, Runtime, WebviewWindow};
use tokio::sync::oneshot;
use tokio::fs;
use tokio::fs::{create_dir_all, File};
use tokio::io::AsyncWriteExt;
use tokio::sync::watch::Receiver;
use tokio::sync::{oneshot, Mutex};
use yaak_models::models::{
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, HttpUrlParameter,
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
HttpResponseState, ProxySetting, ProxySettingAuth,
};
use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_jar};
use yaak_models::queries::{
get_base_environment, get_http_response, get_or_create_settings, get_workspace,
update_response_if_id, upsert_cookie_jar, UpdateSource,
};
use yaak_plugins::events::{RenderPurpose, WindowContext};
pub async fn send_http_request<R: Runtime>(
window: &WebviewWindow<R>,
request: &HttpRequest,
response: &HttpResponse,
og_response: &HttpResponse,
environment: Option<Environment>,
cookie_jar: Option<CookieJar>,
cancel_rx: &mut Receiver<bool>,
cancelled_rx: &mut Receiver<bool>,
) -> Result<HttpResponse, String> {
let workspace = get_workspace(window, &request.workspace_id)
let workspace =
get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace");
let base_environment = get_base_environment(window, &request.workspace_id)
.await
.expect("Failed to get Workspace");
let cb = &*window.app_handle().state::<PluginTemplateCallback>();
let cb = cb.for_send();
.expect("Failed to get base environment");
let settings = get_or_create_settings(window).await;
let cb = PluginTemplateCallback::new(
window.app_handle(),
&WindowContext::from_window(window),
RenderPurpose::Send,
);
let response_id = og_response.id.clone();
let response = Arc::new(Mutex::new(og_response.clone()));
let rendered_request =
render_http_request(&request, &workspace, environment.as_ref(), &cb).await;
render_http_request(&request, &base_environment, environment.as_ref(), &cb).await;
let mut url_string = rendered_request.url;
@@ -50,6 +66,7 @@ pub async fn send_http_request<R: Runtime>(
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
url_string = format!("http://{}", url_string);
}
debug!("Sending request to {url_string}");
let mut client_builder = reqwest::Client::builder()
.redirect(match workspace.setting_follow_redirects {
@@ -61,9 +78,46 @@ pub async fn send_http_request<R: Runtime>(
.brotli(true)
.deflate(true)
.referer(false)
.danger_accept_invalid_certs(!workspace.setting_validate_certificates)
.tls_info(true);
if workspace.setting_validate_certificates {
// Use platform-native verifier to validate certificates
client_builder =
client_builder.use_preconfigured_tls(ClientConfig::with_platform_verifier())
} else {
// Use rustls to skip validation because rustls_platform_verifier does not have this
// ability
client_builder = client_builder
.use_rustls_tls()
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true);
}
match settings.proxy {
Some(ProxySetting::Disabled) => client_builder = client_builder.no_proxy(),
Some(ProxySetting::Enabled { http, https, auth }) => {
debug!("Using proxy http={http} https={https}");
let mut proxy = Proxy::custom(move |url| {
let http = if http.is_empty() { None } else { Some(http.to_owned()) };
let https = if https.is_empty() { None } else { Some(https.to_owned()) };
let proxy_url = match (url.scheme(), http, https) {
("http", Some(proxy_url), _) => Some(proxy_url),
("https", _, Some(proxy_url)) => Some(proxy_url),
_ => None,
};
proxy_url
});
if let Some(ProxySettingAuth { user, password }) = auth {
debug!("Using proxy auth");
proxy = proxy.basic_auth(user.as_str(), password.as_str());
}
client_builder = client_builder.proxy(proxy);
}
None => {} // Nothing to do for this one, as it is the default
}
// Add cookie store if specified
let maybe_cookie_manager = match cookie_jar.clone() {
Some(cj) => {
@@ -103,38 +157,30 @@ pub async fn send_http_request<R: Runtime>(
if !p.enabled || p.name.is_empty() {
continue;
}
// Replace path parameters with values from URL parameters
let old_url_string = url_string.clone();
url_string = replace_path_placeholder(&p, url_string.as_str());
// Treat as regular param if wasn't used as path param
if old_url_string == url_string {
query_params.push((p.name, p.value));
}
query_params.push((p.name, p.value));
}
let uri = match http::Uri::from_str(url_string.as_str()) {
Ok(u) => u,
Err(e) => {
return response_err(
response,
return Ok(response_err(
&*response.lock().await,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window,
)
.await;
.await);
}
};
// Yes, we're parsing both URI and URL because they could return different errors
let url = match Url::from_str(uri.to_string().as_str()) {
Ok(u) => u,
Err(e) => {
return response_err(
response,
return Ok(response_err(
&*response.lock().await,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window,
)
.await;
.await);
}
};
@@ -193,16 +239,8 @@ pub async fn send_http_request<R: Runtime>(
let a = rendered_request.authentication;
if b == "basic" {
let username = a
.get("username")
.unwrap_or(empty_value)
.as_str()
.unwrap_or_default();
let password = a
.get("password")
.unwrap_or(empty_value)
.as_str()
.unwrap_or_default();
let username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or_default();
let password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or_default();
let auth = format!("{username}:{password}");
let encoded = BASE64_STANDARD.encode(auth);
@@ -211,11 +249,7 @@ pub async fn send_http_request<R: Runtime>(
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
);
} else if b == "bearer" {
let token = a
.get("token")
.unwrap_or(empty_value)
.as_str()
.unwrap_or_default();
let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or_default();
headers.insert(
"Authorization",
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
@@ -225,14 +259,11 @@ pub async fn send_http_request<R: Runtime>(
let request_body = rendered_request.body;
if let Some(body_type) = &rendered_request.body_type {
if request_body.contains_key("query") && request_body.contains_key("variables") {
if body_type == "graphql" {
let query = get_str_h(&request_body, "query");
let variables = get_str_h(&request_body, "variables");
let body = if variables.trim().is_empty() {
format!(
r#"{{"query":{}}}"#,
serde_json::to_string(query).unwrap_or_default()
)
format!(r#"{{"query":{}}}"#, serde_json::to_string(query).unwrap_or_default())
} else {
format!(
r#"{{"query":{},"variables":{variables}}}"#,
@@ -240,9 +271,6 @@ pub async fn send_http_request<R: Runtime>(
)
};
request_builder = request_builder.body(body.to_owned());
} else if request_body.contains_key("text") {
let body = get_str_h(&request_body, "text");
request_builder = request_builder.body(body.to_owned());
} else if body_type == "application/x-www-form-urlencoded"
&& request_body.contains_key("form")
{
@@ -272,12 +300,12 @@ pub async fn send_http_request<R: Runtime>(
.as_str()
.unwrap_or_default();
match fs::read(file_path).map_err(|e| e.to_string()) {
match fs::read(file_path).await.map_err(|e| e.to_string()) {
Ok(f) => {
request_builder = request_builder.body(f);
}
Err(e) => {
return response_err(response, e, window).await;
return Ok(response_err(&*response.lock().await, e, window).await);
}
}
} else if body_type == "multipart/form-data" && request_body.contains_key("form") {
@@ -300,10 +328,15 @@ pub async fn send_http_request<R: Runtime>(
let mut part = if file_path.is_empty() {
multipart::Part::text(value.clone())
} else {
match fs::read(file_path.clone()) {
match fs::read(file_path.clone()).await {
Ok(f) => multipart::Part::bytes(f),
Err(e) => {
return response_err(response, e.to_string(), window).await;
return Ok(response_err(
&*response.lock().await,
e.to_string(),
window,
)
.await);
}
}
};
@@ -318,9 +351,8 @@ pub async fn send_http_request<R: Runtime>(
Mime::from_str("application/octet-stream").unwrap();
let mime =
mime_guess::from_path(file_path.clone()).first_or(default_mime);
part = part
.mime_str(mime.essence_str())
.map_err(|e| e.to_string())?;
part =
part.mime_str(mime.essence_str()).map_err(|e| e.to_string())?;
}
// Set file path if not empty
@@ -340,6 +372,9 @@ pub async fn send_http_request<R: Runtime>(
}
headers.remove("Content-Type"); // reqwest will add this automatically
request_builder = request_builder.multipart(multipart_form);
} else if request_body.contains_key("text") {
let body = get_str_h(&request_body, "text");
request_builder = request_builder.body(body.to_owned());
} else {
warn!("Unsupported body type: {}", body_type);
}
@@ -351,118 +386,181 @@ pub async fn send_http_request<R: Runtime>(
let sendable_req = match request_builder.build() {
Ok(r) => r,
Err(e) => {
return response_err(response, e.to_string(), window).await;
warn!("Failed to build request builder {e:?}");
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
}
};
let start = std::time::Instant::now();
let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, reqwest::Error>>();
let (done_tx, done_rx) = oneshot::channel::<HttpResponse>();
let (resp_tx, resp_rx) = oneshot::channel();
let start = std::time::Instant::now();
tokio::spawn(async move {
let _ = resp_tx.send(client.execute(sendable_req).await);
});
let raw_response = tokio::select! {
Ok(r) = resp_rx => {r}
_ = cancel_rx.changed() => {
return response_err(response, "Request was cancelled".to_string(), window).await;
Ok(r) = resp_rx => r,
_ = cancelled_rx.changed() => {
debug!("Request cancelled");
return Ok(response_err(&*response.lock().await, "Request was cancelled".to_string(), window).await);
}
};
match raw_response {
Ok(v) => {
let mut response = response.clone();
response.elapsed_headers = start.elapsed().as_millis() as i32;
let response_headers = v.headers().clone();
response.status = v.status().as_u16() as i32;
response.status_reason = v.status().canonical_reason().map(|s| s.to_string());
response.headers = response_headers
.iter()
.map(|(k, v)| HttpResponseHeader {
name: k.as_str().to_string(),
value: v.to_str().unwrap_or_default().to_string(),
})
.collect();
response.url = v.url().to_string();
response.remote_addr = v.remote_addr().map(|a| a.to_string());
response.version = match v.version() {
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
_ => None,
{
let window = window.clone();
let cancelled_rx = cancelled_rx.clone();
let response_id = response_id.clone();
let response = response.clone();
tokio::spawn(async move {
match raw_response {
Ok(mut v) => {
let content_length = v.content_length();
let response_headers = v.headers().clone();
let dir = window.app_handle().path().app_data_dir().unwrap();
let base_dir = dir.join("responses");
create_dir_all(base_dir.clone()).await.expect("Failed to create responses dir");
let body_path = if response_id.is_empty() {
base_dir.join(uuid::Uuid::new_v4().to_string())
} else {
base_dir.join(response_id.clone())
};
{
let mut r = response.lock().await;
r.body_path = Some(body_path.to_str().unwrap().to_string());
r.elapsed_headers = start.elapsed().as_millis() as i32;
r.status = v.status().as_u16() as i32;
r.status_reason = v.status().canonical_reason().map(|s| s.to_string());
r.headers = response_headers
.iter()
.map(|(k, v)| HttpResponseHeader {
name: k.as_str().to_string(),
value: v.to_str().unwrap_or_default().to_string(),
})
.collect();
r.url = v.url().to_string();
r.remote_addr = v.remote_addr().map(|a| a.to_string());
r.version = match v.version() {
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
_ => None,
};
r.state = HttpResponseState::Connected;
update_response_if_id(&window, &r, &UpdateSource::Window)
.await
.expect("Failed to update response after connected");
}
// Write body to FS
let mut f = File::options()
.create(true)
.truncate(true)
.write(true)
.open(&body_path)
.await
.expect("Failed to open file");
let mut written_bytes: usize = 0;
loop {
let chunk = v.chunk().await;
if *cancelled_rx.borrow() {
// Request was canceled
return;
}
match chunk {
Ok(Some(bytes)) => {
let mut r = response.lock().await;
r.elapsed = start.elapsed().as_millis() as i32;
f.write_all(&bytes).await.expect("Failed to write to file");
f.flush().await.expect("Failed to flush file");
written_bytes += bytes.len();
r.content_length = Some(written_bytes as i32);
update_response_if_id(&window, &r, &UpdateSource::Window)
.await
.expect("Failed to update response");
}
Ok(None) => {
break;
}
Err(e) => {
response_err(&*response.lock().await, e.to_string(), &window).await;
break;
}
}
}
// Set final content length
{
let mut r = response.lock().await;
r.content_length = match content_length {
Some(l) => Some(l as i32),
None => Some(written_bytes as i32),
};
r.state = HttpResponseState::Closed;
update_response_if_id(&window, &r, &UpdateSource::Window)
.await
.expect("Failed to update response");
};
// Add cookie store if specified
if let Some((cookie_store, mut cookie_jar)) = maybe_cookie_manager {
// let cookies = response_headers.get_all(SET_COOKIE).iter().map(|h| {
// println!("RESPONSE COOKIE: {}", h.to_str().unwrap());
// cookie_store::RawCookie::from_str(h.to_str().unwrap())
// .expect("Failed to parse cookie")
// });
// store.store_response_cookies(cookies, &url);
let json_cookies: Vec<Cookie> = cookie_store
.lock()
.unwrap()
.iter_any()
.map(|c| {
let json_cookie =
serde_json::to_value(&c).expect("Failed to serialize cookie");
serde_json::from_value(json_cookie)
.expect("Failed to deserialize cookie")
})
.collect::<Vec<_>>();
cookie_jar.cookies = json_cookies;
if let Err(e) =
upsert_cookie_jar(&window, &cookie_jar, &UpdateSource::Window).await
{
error!("Failed to update cookie jar: {}", e);
};
}
}
Err(e) => {
warn!("Failed to execute request {e}");
response_err(&*response.lock().await, format!("{e}{e:?}"), &window).await;
}
};
let content_length = v.content_length();
let body_bytes = v.bytes().await.expect("Failed to get body").to_vec();
response.elapsed = start.elapsed().as_millis() as i32;
let r = response.lock().await.clone();
done_tx.send(r).unwrap();
});
};
// Use content length if available, otherwise use body length
response.content_length = match content_length {
Some(l) => Some(l as i32),
None => Some(body_bytes.len() as i32),
};
{
// Write body to FS
let dir = window.app_handle().path().app_data_dir().unwrap();
let base_dir = dir.join("responses");
create_dir_all(base_dir.clone()).expect("Failed to create responses dir");
let body_path = match response.id.is_empty() {
false => base_dir.join(response.id.clone()),
true => base_dir.join(uuid::Uuid::new_v4().to_string()),
};
let mut f = File::options()
.create(true)
.truncate(true)
.write(true)
.open(&body_path)
.expect("Failed to open file");
f.write_all(body_bytes.as_slice())
.expect("Failed to write to file");
response.body_path = Some(
body_path
.to_str()
.expect("Failed to get body path")
.to_string(),
);
Ok(tokio::select! {
Ok(r) = done_rx => r,
_ = cancelled_rx.changed() => {
match get_http_response(window, response_id.as_str()).await {
Ok(mut r) => {
r.state = HttpResponseState::Closed;
update_response_if_id(&window, &r, &UpdateSource::Window).await.expect("Failed to update response")
},
_ => {
response_err(&*response.lock().await, "Ephemeral request was cancelled".to_string(), &window).await
}.clone(),
}
response = update_response_if_id(window, &response)
.await
.expect("Failed to update response");
// Add cookie store if specified
if let Some((cookie_store, mut cookie_jar)) = maybe_cookie_manager {
// let cookies = response_headers.get_all(SET_COOKIE).iter().map(|h| {
// println!("RESPONSE COOKIE: {}", h.to_str().unwrap());
// cookie_store::RawCookie::from_str(h.to_str().unwrap())
// .expect("Failed to parse cookie")
// });
// store.store_response_cookies(cookies, &url);
let json_cookies: Vec<Cookie> = cookie_store
.lock()
.unwrap()
.iter_any()
.map(|c| {
let json_cookie =
serde_json::to_value(&c).expect("Failed to serialize cookie");
serde_json::from_value(json_cookie).expect("Failed to deserialize cookie")
})
.collect::<Vec<_>>();
cookie_jar.cookies = json_cookies;
if let Err(e) = upsert_cookie_jar(window, &cookie_jar).await {
error!("Failed to update cookie jar: {}", e);
};
}
Ok(response)
}
Err(e) => response_err(response, e.to_string(), window).await,
}
})
}
fn ensure_proto(url_str: &str) -> String {
@@ -508,123 +606,3 @@ fn get_str_h<'a>(v: &'a BTreeMap<String, Value>, key: &str) -> &'a str {
Some(v) => v.as_str().unwrap_or_default(),
}
}
fn replace_path_placeholder(p: &HttpUrlParameter, url: &str) -> String {
if !p.enabled {
return url.to_string();
}
if !p.name.starts_with(":") {
return url.to_string();
}
let re = regex::Regex::new(format!("(/){}([/?#]|$)", p.name).as_str()).unwrap();
let result = re
.replace_all(url, |cap: &regex::Captures| {
format!(
"{}{}{}",
cap[1].to_string(),
urlencoding::encode(p.value.as_str()),
cap[2].to_string()
)
})
.into_owned();
result
}
#[cfg(test)]
mod tests {
use crate::http_request::replace_path_placeholder;
use yaak_models::models::HttpUrlParameter;
#[test]
fn placeholder_middle() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo/bar"),
"https://example.com/xxx/bar",
);
}
#[test]
fn placeholder_end() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/xxx",
);
}
#[test]
fn placeholder_query() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo?:foo"),
"https://example.com/xxx?:foo",
);
}
#[test]
fn placeholder_missing() {
let p = HttpUrlParameter {
enabled: true,
name: "".to_string(),
value: "".to_string(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:missing"),
"https://example.com/:missing",
);
}
#[test]
fn placeholder_disabled() {
let p = HttpUrlParameter {
enabled: false,
name: ":foo".to_string(),
value: "xxx".to_string(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/:foo",
);
}
#[test]
fn placeholder_prefix() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foooo"),
"https://example.com/:foooo",
);
}
#[test]
fn placeholder_encode() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "Hello World".into(),
enabled: true,
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/Hello%20World",
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ use log::debug;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager, Runtime, WebviewWindow};
use yaak_models::queries::{get_key_value_raw, set_key_value_raw};
use yaak_models::queries::{get_key_value_raw, set_key_value_raw, UpdateSource};
// Check for updates every hour
const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60;
@@ -47,11 +47,11 @@ impl YaakNotifier {
seen.push(id.to_string());
debug!("Marked notification as seen {}", id);
let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?;
set_key_value_raw(w, KV_NAMESPACE, KV_KEY, seen_json.as_str()).await;
set_key_value_raw(w, KV_NAMESPACE, KV_KEY, seen_json.as_str(), &UpdateSource::Window).await;
Ok(())
}
pub async fn check<R: Runtime>(&mut self, w: &WebviewWindow<R>) -> Result<(), String> {
pub async fn check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<(), String> {
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
if ignore_check {
@@ -60,8 +60,8 @@ impl YaakNotifier {
self.last_check = SystemTime::now();
let num_launches = get_num_launches(w).await;
let info = w.app_handle().package_info().clone();
let num_launches = get_num_launches(window).await;
let info = window.app_handle().package_info().clone();
let req = reqwest::Client::default()
.request(Method::GET, "https://notify.yaak.app/notifications")
.query(&[
@@ -74,20 +74,17 @@ impl YaakNotifier {
return Ok(());
}
let notification = resp
.json::<YaakNotification>()
.await
.map_err(|e| e.to_string())?;
let notification = resp.json::<YaakNotification>().await.map_err(|e| e.to_string())?;
let age = notification.timestamp.signed_duration_since(Utc::now());
let seen = get_kv(w).await?;
let seen = get_kv(window).await?;
if seen.contains(&notification.id) || (age > Duration::days(2)) {
debug!("Already seen notification {}", notification.id);
return Ok(());
}
debug!("Got notification {:?}", notification);
let _ = w.emit("notification", notification.clone());
let _ = window.emit_to(window.label(), "notification", notification.clone());
Ok(())
}

View File

@@ -1,32 +1,39 @@
use crate::template_callback::PluginTemplateCallback;
use serde_json::{json, Map, Value};
use std::collections::{BTreeMap, HashMap};
use tauri::{AppHandle, Manager, Runtime};
use yaak_models::models::{
Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest,
HttpRequestHeader, HttpUrlParameter, Workspace,
HttpRequestHeader, HttpUrlParameter,
};
use yaak_templates::{parse_and_render, TemplateCallback};
pub async fn render_template<R: Runtime>(
app_handle: &AppHandle<R>,
pub async fn render_template<T: TemplateCallback>(
template: &str,
w: &Workspace,
e: Option<&Environment>,
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> String {
let cb = &*app_handle.state::<PluginTemplateCallback>();
let vars = &make_vars_hashmap(w, e);
let vars = &make_vars_hashmap(base_environment, environment);
render(template, vars, cb).await
}
pub async fn render_grpc_request<R: Runtime>(
app_handle: &AppHandle<R>,
pub async fn render_json_value<T: TemplateCallback>(
value: Value,
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> Value {
let vars = &make_vars_hashmap(base_environment, environment);
render_json_value_raw(value, vars, cb).await
}
pub async fn render_grpc_request<T: TemplateCallback>(
r: &GrpcRequest,
w: &Workspace,
e: Option<&Environment>,
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> GrpcRequest {
let cb = &*app_handle.state::<PluginTemplateCallback>();
let vars = &make_vars_hashmap(w, e);
let vars = &make_vars_hashmap(base_environment, environment);
let mut metadata = Vec::new();
for p in r.metadata.clone() {
@@ -34,12 +41,13 @@ pub async fn render_grpc_request<R: Runtime>(
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
id: p.id,
})
}
let mut authentication = BTreeMap::new();
for (k, v) in r.authentication.clone() {
authentication.insert(k, render_json_value(v, vars, cb).await);
authentication.insert(k, render_json_value_raw(v, vars, cb).await);
}
let url = render(r.url.as_str(), vars, cb).await;
@@ -54,11 +62,11 @@ pub async fn render_grpc_request<R: Runtime>(
pub async fn render_http_request(
r: &HttpRequest,
w: &Workspace,
e: Option<&Environment>,
base_environment: &Environment,
environment: Option<&Environment>,
cb: &PluginTemplateCallback,
) -> HttpRequest {
let vars = &make_vars_hashmap(w, e);
let vars = &make_vars_hashmap(base_environment, environment);
let mut url_parameters = Vec::new();
for p in r.url_parameters.clone() {
@@ -66,6 +74,7 @@ pub async fn render_http_request(
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
id: p.id,
})
}
@@ -75,36 +84,40 @@ pub async fn render_http_request(
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
id: p.id,
})
}
let mut body = BTreeMap::new();
for (k, v) in r.body.clone() {
body.insert(k, render_json_value(v, vars, cb).await);
body.insert(k, render_json_value_raw(v, vars, cb).await);
}
let mut authentication = BTreeMap::new();
for (k, v) in r.authentication.clone() {
authentication.insert(k, render_json_value(v, vars, cb).await);
authentication.insert(k, render_json_value_raw(v, vars, cb).await);
}
let url = render(r.url.clone().as_str(), vars, cb).await;
HttpRequest {
let req = HttpRequest {
url,
url_parameters,
headers,
body,
authentication,
..r.to_owned()
}
};
// This doesn't fit perfectly with the concept of "rendering" but it kind of does
apply_path_placeholders(req)
}
pub fn make_vars_hashmap(
workspace: &Workspace,
base_environment: &Environment,
environment: Option<&Environment>,
) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &workspace.variables);
variables = add_variable_to_map(variables, &base_environment.variables);
if let Some(e) = environment {
variables = add_variable_to_map(variables, &e.variables);
@@ -138,7 +151,7 @@ fn add_variable_to_map(
map
}
pub async fn render_json_value<T: TemplateCallback>(
async fn render_json_value_raw<T: TemplateCallback>(
v: Value,
vars: &HashMap<String, String>,
cb: &T,
@@ -148,7 +161,7 @@ pub async fn render_json_value<T: TemplateCallback>(
Value::Array(a) => {
let mut new_a = Vec::new();
for v in a {
new_a.push(Box::pin(render_json_value(v, vars, cb)).await)
new_a.push(Box::pin(render_json_value_raw(v, vars, cb)).await)
}
json!(new_a)
}
@@ -156,7 +169,7 @@ pub async fn render_json_value<T: TemplateCallback>(
let mut new_o = Map::new();
for (k, v) in o {
let key = Box::pin(render(k.as_str(), vars, cb)).await;
let value = Box::pin(render_json_value(v, vars, cb)).await;
let value = Box::pin(render_json_value_raw(v, vars, cb)).await;
new_o.insert(key, value);
}
json!(new_o)
@@ -166,7 +179,7 @@ pub async fn render_json_value<T: TemplateCallback>(
}
#[cfg(test)]
mod tests {
mod render_tests {
use serde_json::json;
use std::collections::HashMap;
use yaak_templates::TemplateCallback;
@@ -189,7 +202,7 @@ mod tests {
let mut vars = HashMap::new();
vars.insert("a".to_string(), "aaa".to_string());
let result = super::render_json_value(v, &vars, &EmptyCB {}).await;
let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await;
assert_eq!(result, json!("aaa"))
}
@@ -199,7 +212,7 @@ mod tests {
let mut vars = HashMap::new();
vars.insert("a".to_string(), "aaa".to_string());
let result = super::render_json_value(v, &vars, &EmptyCB {}).await;
let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await;
assert_eq!(result, json!(["aaa", "aaa"]))
}
@@ -209,7 +222,7 @@ mod tests {
let mut vars = HashMap::new();
vars.insert("a".to_string(), "aaa".to_string());
let result = super::render_json_value(v, &vars, &EmptyCB {}).await;
let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await;
assert_eq!(result, json!({"aaa": "aaa"}))
}
@@ -226,7 +239,7 @@ mod tests {
let mut vars = HashMap::new();
vars.insert("a".to_string(), "aaa".to_string());
let result = super::render_json_value(v, &vars, &EmptyCB {}).await;
let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await;
assert_eq!(
result,
json!([
@@ -240,3 +253,180 @@ mod tests {
)
}
}
fn replace_path_placeholder(p: &HttpUrlParameter, url: &str) -> String {
if !p.enabled {
return url.to_string();
}
if !p.name.starts_with(":") {
return url.to_string();
}
let re = regex::Regex::new(format!("(/){}([/?#]|$)", p.name).as_str()).unwrap();
let result = re
.replace_all(url, |cap: &regex::Captures| {
format!(
"{}{}{}",
cap[1].to_string(),
urlencoding::encode(p.value.as_str()),
cap[2].to_string()
)
})
.into_owned();
result
}
fn apply_path_placeholders(rendered_request: HttpRequest) -> HttpRequest {
let mut url = rendered_request.url.to_owned();
let mut url_parameters = Vec::new();
for p in rendered_request.url_parameters.clone() {
if !p.enabled || p.name.is_empty() {
continue;
}
// Replace path parameters with values from URL parameters
let old_url_string = url.clone();
url = replace_path_placeholder(&p, url.as_str());
// Remove as param if it modified the URL
if old_url_string == url {
url_parameters.push(p);
}
}
let mut request = rendered_request.clone();
request.url_parameters = url_parameters;
request.url = url;
request
}
#[cfg(test)]
mod placeholder_tests {
use crate::render::{apply_path_placeholders, replace_path_placeholder};
use yaak_models::models::{HttpRequest, HttpUrlParameter};
#[test]
fn placeholder_middle() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo/bar"),
"https://example.com/xxx/bar",
);
}
#[test]
fn placeholder_end() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/xxx",
);
}
#[test]
fn placeholder_query() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo?:foo"),
"https://example.com/xxx?:foo",
);
}
#[test]
fn placeholder_missing() {
let p = HttpUrlParameter {
enabled: true,
name: "".to_string(),
value: "".to_string(),
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:missing"),
"https://example.com/:missing",
);
}
#[test]
fn placeholder_disabled() {
let p = HttpUrlParameter {
enabled: false,
name: ":foo".to_string(),
value: "xxx".to_string(),
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/:foo",
);
}
#[test]
fn placeholder_prefix() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "xxx".into(),
enabled: true,
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foooo"),
"https://example.com/:foooo",
);
}
#[test]
fn placeholder_encode() {
let p = HttpUrlParameter {
name: ":foo".into(),
value: "Hello World".into(),
enabled: true,
id: "p1".into(),
};
assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"),
"https://example.com/Hello%20World",
);
}
#[test]
fn apply_placeholder() {
let result = apply_path_placeholders(HttpRequest {
url: "example.com/:a/bar".to_string(),
url_parameters: vec![
HttpUrlParameter {
name: "b".to_string(),
value: "bbb".to_string(),
enabled: true,
id: "p1".into(),
},
HttpUrlParameter {
name: ":a".to_string(),
value: "aaa".to_string(),
enabled: true,
id: "p2".into(),
},
],
..Default::default()
});
assert_eq!(result.url, "example.com/aaa/bar");
assert_eq!(result.url_parameters.len(), 1);
assert_eq!(result.url_parameters[0].name, "b");
assert_eq!(result.url_parameters[0].value, "bbb");
}
}

View File

@@ -1,3 +1,4 @@
use crate::MAIN_WINDOW_PREFIX;
use hex_color::HexColor;
use log::warn;
use objc::{msg_send, sel, sel_impl};
@@ -6,7 +7,6 @@ use tauri::{
plugin::{Builder, TauriPlugin},
Emitter, Listener, Manager, Runtime, Window, WindowEvent,
};
use crate::MAIN_WINDOW_PREFIX;
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
@@ -202,10 +202,9 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
}
unsafe {
let ns_win = window
.ns_window()
.expect("NS Window should exist to mount traffic light delegate.")
as id;
let ns_win =
window.ns_window().expect("NS Window should exist to mount traffic light delegate.")
as id;
let current_delegate: id = ns_win.delegate();
@@ -322,10 +321,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-enter-fullscreen", ())
.expect("Failed to emit event");
state.window.emit("did-enter-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -339,10 +335,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-enter-fullscreen", ())
.expect("Failed to emit event");
state.window.emit("will-enter-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -356,10 +349,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-exit-fullscreen", ())
.expect("Failed to emit event");
state.window.emit("did-exit-fullscreen", ()).expect("Failed to emit event");
let id = state.window.ns_window().expect("Failed to emit event") as id;
position_traffic_lights(
@@ -381,10 +371,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
) {
unsafe {
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-exit-fullscreen", ())
.expect("Failed to emit event");
state.window.emit("will-exit-fullscreen", ()).expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -432,11 +419,8 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
window: window.clone(),
};
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();
let random_str: String =
rand::thread_rng().sample_iter(&Alphanumeric).take(20).map(char::from).collect();
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.

View File

@@ -1,43 +1,41 @@
use std::collections::HashMap;
use tauri::{AppHandle, Manager};
use yaak_plugin_runtime::events::{RenderPurpose, TemplateFunctionArg};
use yaak_plugin_runtime::manager::PluginManager;
use tauri::{AppHandle, Manager, Runtime};
use yaak_plugins::events::{RenderPurpose, TemplateFunctionArg, WindowContext};
use yaak_plugins::manager::PluginManager;
use yaak_templates::TemplateCallback;
#[derive(Clone)]
pub struct PluginTemplateCallback {
app_handle: AppHandle,
purpose: RenderPurpose,
plugin_manager: PluginManager,
window_context: WindowContext,
render_purpose: RenderPurpose,
}
impl PluginTemplateCallback {
pub fn new(app_handle: AppHandle) -> PluginTemplateCallback {
pub fn new<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &WindowContext,
render_purpose: RenderPurpose,
) -> PluginTemplateCallback {
let plugin_manager = &*app_handle.state::<PluginManager>();
PluginTemplateCallback {
app_handle,
purpose: RenderPurpose::Preview,
plugin_manager: plugin_manager.to_owned(),
window_context: window_context.to_owned(),
render_purpose,
}
}
pub fn for_send(&self) -> PluginTemplateCallback {
let mut v = self.clone();
v.purpose = RenderPurpose::Send;
v
}
}
impl TemplateCallback for PluginTemplateCallback {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String, String> {
let window_context = self.window_context.to_owned();
// The beta named the function `Response` but was changed in stable.
// Keep this here for a while because there's no easy way to migrate
let fn_name = if fn_name == "Response" {
"response"
} else {
fn_name
};
let fn_name = if fn_name == "Response" { "response" } else { fn_name };
let plugin_manager = self.app_handle.state::<PluginManager>();
let function = plugin_manager
.get_template_functions()
let function = self
.plugin_manager
.get_template_functions_with_context(window_context.to_owned())
.await
.map_err(|e| e.to_string())?
.iter()
@@ -53,6 +51,7 @@ impl TemplateCallback for PluginTemplateCallback {
TemplateFunctionArg::Text(a) => a.base,
TemplateFunctionArg::Select(a) => a.base,
TemplateFunctionArg::Checkbox(a) => a.base,
TemplateFunctionArg::File(a) => a.base,
TemplateFunctionArg::HttpRequest(a) => a.base,
};
if let None = args_with_defaults.get(base.name.as_str()) {
@@ -60,8 +59,14 @@ impl TemplateCallback for PluginTemplateCallback {
}
}
let resp = plugin_manager
.call_template_function(fn_name, args_with_defaults, self.purpose.clone())
let resp = self
.plugin_manager
.call_template_function(
window_context,
fn_name,
args_with_defaults,
self.render_purpose.to_owned(),
)
.await
.map_err(|e| e.to_string())?;
Ok(resp.unwrap_or_default())

View File

@@ -3,10 +3,10 @@ use std::time::SystemTime;
use log::info;
use tauri::{AppHandle, Manager};
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons};
use tauri_plugin_updater::UpdaterExt;
use tokio::task::block_in_place;
use yaak_plugin_runtime::manager::PluginManager;
use yaak_plugins::manager::PluginManager;
use crate::is_dev;
@@ -92,8 +92,10 @@ impl YaakUpdater {
"{} is available. Would you like to download and install it now?",
update.version
))
.ok_button_label("Download")
.cancel_button_label("Later")
.buttons(MessageDialogButtons::OkCancelCustom(
"Download".to_string(),
"Later".to_string(),
))
.title("Update Available")
.show(|confirmed| {
if !confirmed {
@@ -105,8 +107,10 @@ impl YaakUpdater {
if h.dialog()
.message("Would you like to restart the app?")
.title("Update Installed")
.ok_button_label("Restart")
.cancel_button_label("Later")
.buttons(MessageDialogButtons::OkCancelCustom(
"Restart".to_string(),
"Later".to_string(),
))
.blocking_show()
{
h.restart();

View File

@@ -45,8 +45,8 @@
"active": true,
"category": "DeveloperTool",
"externalBin": [
"vendored/protoc/yaakprotoc",
"vendored/node/yaaknode"
"vendored/node/yaaknode",
"vendored/protoc/yaakprotoc"
],
"icon": [
"icons/release/32x32.png",
@@ -72,17 +72,14 @@
"rpm"
],
"createUpdaterArtifacts": "v1Compatible",
"iOS": {
"developmentTeam": "7PU3P6ELJ8"
},
"macOS": {
"minimumSystemVersion": "13.0",
"exceptionDomain": "",
"entitlements": "macos/entitlements.plist",
"frameworks": []
},
"windows": {
"digestAlgorithm": "sha256",
"timestampUrl": ""
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a Yaak -c yaakapp %1"
}
}
}

View File

@@ -0,0 +1,109 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin,
pluginHookExport: () => pluginHookExport
});
module.exports = __toCommonJS(src_exports);
var NEWLINE = "\\\n ";
var plugin = {
httpRequestActions: [{
key: "export-curl",
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 pluginHookExport(ctx, rendered_request);
ctx.clipboard.copyText(data);
ctx.toast.show({ message: "Curl copied to clipboard", icon: "copy" });
}
}]
};
async function pluginHookExport(_ctx, request) {
const xs = ["curl"];
if (request.method) xs.push("-X", request.method);
if (request.url) xs.push(quote(request.url));
xs.push(NEWLINE);
for (const p of (request.urlParameters ?? []).filter(onlyEnabled)) {
xs.push("--url-query", quote(`${p.name}=${p.value}`));
xs.push(NEWLINE);
}
for (const h of (request.headers ?? []).filter(onlyEnabled)) {
xs.push("--header", quote(`${h.name}: ${h.value}`));
xs.push(NEWLINE);
}
if (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 (typeof request.body?.query === "string") {
const body = { query: request.body.query || "", variables: maybeParseJSON(request.body.variables, void 0) };
xs.push("--data-raw", `${quote(JSON.stringify(body))}`);
xs.push(NEWLINE);
} else if (typeof request.body?.text === "string") {
xs.push("--data-raw", `${quote(request.body.text)}`);
xs.push(NEWLINE);
}
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);
}
if (request.authenticationType === "bearer") {
xs.push("--header", quote(`Authorization: Bearer ${request.authentication?.token ?? ""}`));
xs.push(NEWLINE);
}
if (xs[xs.length - 1] === NEWLINE) {
xs.splice(xs.length - 1, 1);
}
return xs.join(" ");
}
function quote(arg) {
const escaped = arg.replace(/'/g, "\\'");
return `'${escaped}'`;
}
function onlyEnabled(v) {
return v.enabled !== false && !!v.name;
}
function maybeParseJSON(v, fallback) {
try {
return JSON.parse(v);
} catch (err) {
return fallback;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin,
pluginHookExport
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/exporter-curl",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,510 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookResponseFilter: () => pluginHookResponseFilter
});
module.exports = __toCommonJS(src_exports);
// ../../node_modules/jsonpath-plus/dist/index-node-esm.js
var import_vm = __toESM(require("vm"), 1);
var {
hasOwnProperty: hasOwnProp
} = Object.prototype;
function push(arr, item) {
arr = arr.slice();
arr.push(item);
return arr;
}
function unshift(item, arr) {
arr = arr.slice();
arr.unshift(item);
return arr;
}
var NewError = class extends Error {
/**
* @param {AnyResult} value The evaluated scalar value
*/
constructor(value) {
super('JSONPath should not be called with "new" (it prevents return of (unwrapped) scalar values)');
this.avoidNew = true;
this.value = value;
this.name = "NewError";
}
};
function JSONPath(opts, expr, obj, callback, otherTypeCallback) {
if (!(this instanceof JSONPath)) {
try {
return new JSONPath(opts, expr, obj, callback, otherTypeCallback);
} catch (e) {
if (!e.avoidNew) {
throw e;
}
return e.value;
}
}
if (typeof opts === "string") {
otherTypeCallback = callback;
callback = obj;
obj = expr;
expr = opts;
opts = null;
}
const optObj = opts && typeof opts === "object";
opts = opts || {};
this.json = opts.json || obj;
this.path = opts.path || expr;
this.resultType = opts.resultType || "value";
this.flatten = opts.flatten || false;
this.wrap = hasOwnProp.call(opts, "wrap") ? opts.wrap : true;
this.sandbox = opts.sandbox || {};
this.eval = opts.eval === void 0 ? "safe" : opts.eval;
this.ignoreEvalErrors = typeof opts.ignoreEvalErrors === "undefined" ? false : opts.ignoreEvalErrors;
this.parent = opts.parent || null;
this.parentProperty = opts.parentProperty || null;
this.callback = opts.callback || callback || null;
this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function() {
throw new TypeError("You must supply an otherTypeCallback callback option with the @other() operator.");
};
if (opts.autostart !== false) {
const args = {
path: optObj ? opts.path : expr
};
if (!optObj) {
args.json = obj;
} else if ("json" in opts) {
args.json = opts.json;
}
const ret = this.evaluate(args);
if (!ret || typeof ret !== "object") {
throw new NewError(ret);
}
return ret;
}
}
JSONPath.prototype.evaluate = function(expr, json, callback, otherTypeCallback) {
let currParent = this.parent, currParentProperty = this.parentProperty;
let {
flatten,
wrap
} = this;
this.currResultType = this.resultType;
this.currEval = this.eval;
this.currSandbox = this.sandbox;
callback = callback || this.callback;
this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
json = json || this.json;
expr = expr || this.path;
if (expr && typeof expr === "object" && !Array.isArray(expr)) {
if (!expr.path && expr.path !== "") {
throw new TypeError('You must supply a "path" property when providing an object argument to JSONPath.evaluate().');
}
if (!hasOwnProp.call(expr, "json")) {
throw new TypeError('You must supply a "json" property when providing an object argument to JSONPath.evaluate().');
}
({
json
} = expr);
flatten = hasOwnProp.call(expr, "flatten") ? expr.flatten : flatten;
this.currResultType = hasOwnProp.call(expr, "resultType") ? expr.resultType : this.currResultType;
this.currSandbox = hasOwnProp.call(expr, "sandbox") ? expr.sandbox : this.currSandbox;
wrap = hasOwnProp.call(expr, "wrap") ? expr.wrap : wrap;
this.currEval = hasOwnProp.call(expr, "eval") ? expr.eval : this.currEval;
callback = hasOwnProp.call(expr, "callback") ? expr.callback : callback;
this.currOtherTypeCallback = hasOwnProp.call(expr, "otherTypeCallback") ? expr.otherTypeCallback : this.currOtherTypeCallback;
currParent = hasOwnProp.call(expr, "parent") ? expr.parent : currParent;
currParentProperty = hasOwnProp.call(expr, "parentProperty") ? expr.parentProperty : currParentProperty;
expr = expr.path;
}
currParent = currParent || null;
currParentProperty = currParentProperty || null;
if (Array.isArray(expr)) {
expr = JSONPath.toPathString(expr);
}
if (!expr && expr !== "" || !json) {
return void 0;
}
const exprList = JSONPath.toPathArray(expr);
if (exprList[0] === "$" && exprList.length > 1) {
exprList.shift();
}
this._hasParentSelector = null;
const result = this._trace(exprList, json, ["$"], currParent, currParentProperty, callback).filter(function(ea) {
return ea && !ea.isParentSelector;
});
if (!result.length) {
return wrap ? [] : void 0;
}
if (!wrap && result.length === 1 && !result[0].hasArrExpr) {
return this._getPreferredOutput(result[0]);
}
return result.reduce((rslt, ea) => {
const valOrPath = this._getPreferredOutput(ea);
if (flatten && Array.isArray(valOrPath)) {
rslt = rslt.concat(valOrPath);
} else {
rslt.push(valOrPath);
}
return rslt;
}, []);
};
JSONPath.prototype._getPreferredOutput = function(ea) {
const resultType = this.currResultType;
switch (resultType) {
case "all": {
const path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
ea.pointer = JSONPath.toPointer(path);
ea.path = typeof ea.path === "string" ? ea.path : JSONPath.toPathString(ea.path);
return ea;
}
case "value":
case "parent":
case "parentProperty":
return ea[resultType];
case "path":
return JSONPath.toPathString(ea[resultType]);
case "pointer":
return JSONPath.toPointer(ea.path);
default:
throw new TypeError("Unknown result type");
}
};
JSONPath.prototype._handleCallback = function(fullRetObj, callback, type) {
if (callback) {
const preferredOutput = this._getPreferredOutput(fullRetObj);
fullRetObj.path = typeof fullRetObj.path === "string" ? fullRetObj.path : JSONPath.toPathString(fullRetObj.path);
callback(preferredOutput, type, fullRetObj);
}
};
JSONPath.prototype._trace = function(expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
let retObj;
if (!expr.length) {
retObj = {
path,
value: val,
parent,
parentProperty: parentPropName,
hasArrExpr
};
this._handleCallback(retObj, callback, "value");
return retObj;
}
const loc = expr[0], x = expr.slice(1);
const ret = [];
function addRet(elems) {
if (Array.isArray(elems)) {
elems.forEach((t) => {
ret.push(t);
});
} else {
ret.push(elems);
}
}
if ((typeof loc !== "string" || literalPriority) && val && hasOwnProp.call(val, loc)) {
addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr));
} else if (loc === "*") {
this._walk(val, (m) => {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true, true));
});
} else if (loc === "..") {
addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr));
this._walk(val, (m) => {
if (typeof val[m] === "object") {
addRet(this._trace(expr.slice(), val[m], push(path, m), val, m, callback, true));
}
});
} else if (loc === "^") {
this._hasParentSelector = true;
return {
path: path.slice(0, -1),
expr: x,
isParentSelector: true
};
} else if (loc === "~") {
retObj = {
path: push(path, loc),
value: parentPropName,
parent,
parentProperty: null
};
this._handleCallback(retObj, callback, "property");
return retObj;
} else if (loc === "$") {
addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));
} else if (/^(-?\d*):(-?\d*):?(\d*)$/u.test(loc)) {
addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
} else if (loc.indexOf("?(") === 0) {
if (this.currEval === false) {
throw new Error("Eval [?(expr)] prevented in JSONPath expression.");
}
const safeLoc = loc.replace(/^\?\((.*?)\)$/u, "$1");
const nested = /@.?([^?]*)[['](\??\(.*?\))(?!.\)\])[\]']/gu.exec(safeLoc);
if (nested) {
this._walk(val, (m) => {
const npath = [nested[2]];
const nvalue = nested[1] ? val[m][nested[1]] : val[m];
const filterResults = this._trace(npath, nvalue, path, parent, parentPropName, callback, true);
if (filterResults.length > 0) {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
}
});
} else {
this._walk(val, (m) => {
if (this._eval(safeLoc, val[m], m, path, parent, parentPropName)) {
addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
}
});
}
} else if (loc[0] === "(") {
if (this.currEval === false) {
throw new Error("Eval [(expr)] prevented in JSONPath expression.");
}
addRet(this._trace(unshift(this._eval(loc, val, path[path.length - 1], path.slice(0, -1), parent, parentPropName), x), val, path, parent, parentPropName, callback, hasArrExpr));
} else if (loc[0] === "@") {
let addType = false;
const valueType = loc.slice(1, -2);
switch (valueType) {
case "scalar":
if (!val || !["object", "function"].includes(typeof val)) {
addType = true;
}
break;
case "boolean":
case "string":
case "undefined":
case "function":
if (typeof val === valueType) {
addType = true;
}
break;
case "integer":
if (Number.isFinite(val) && !(val % 1)) {
addType = true;
}
break;
case "number":
if (Number.isFinite(val)) {
addType = true;
}
break;
case "nonFinite":
if (typeof val === "number" && !Number.isFinite(val)) {
addType = true;
}
break;
case "object":
if (val && typeof val === valueType) {
addType = true;
}
break;
case "array":
if (Array.isArray(val)) {
addType = true;
}
break;
case "other":
addType = this.currOtherTypeCallback(val, path, parent, parentPropName);
break;
case "null":
if (val === null) {
addType = true;
}
break;
default:
throw new TypeError("Unknown value type " + valueType);
}
if (addType) {
retObj = {
path,
value: val,
parent,
parentProperty: parentPropName
};
this._handleCallback(retObj, callback, "value");
return retObj;
}
} else if (loc[0] === "`" && val && hasOwnProp.call(val, loc.slice(1))) {
const locProp = loc.slice(1);
addRet(this._trace(x, val[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true));
} else if (loc.includes(",")) {
const parts = loc.split(",");
for (const part of parts) {
addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true));
}
} else if (!literalPriority && val && hasOwnProp.call(val, loc)) {
addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr, true));
}
if (this._hasParentSelector) {
for (let t = 0; t < ret.length; t++) {
const rett = ret[t];
if (rett && rett.isParentSelector) {
const tmp = this._trace(rett.expr, val, rett.path, parent, parentPropName, callback, hasArrExpr);
if (Array.isArray(tmp)) {
ret[t] = tmp[0];
const tl = tmp.length;
for (let tt = 1; tt < tl; tt++) {
t++;
ret.splice(t, 0, tmp[tt]);
}
} else {
ret[t] = tmp;
}
}
}
}
return ret;
};
JSONPath.prototype._walk = function(val, f) {
if (Array.isArray(val)) {
const n = val.length;
for (let i = 0; i < n; i++) {
f(i);
}
} else if (val && typeof val === "object") {
Object.keys(val).forEach((m) => {
f(m);
});
}
};
JSONPath.prototype._slice = function(loc, expr, val, path, parent, parentPropName, callback) {
if (!Array.isArray(val)) {
return void 0;
}
const len = val.length, parts = loc.split(":"), step = parts[2] && Number.parseInt(parts[2]) || 1;
let start = parts[0] && Number.parseInt(parts[0]) || 0, end = parts[1] && Number.parseInt(parts[1]) || len;
start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);
end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);
const ret = [];
for (let i = start; i < end; i += step) {
const tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true);
tmp.forEach((t) => {
ret.push(t);
});
}
return ret;
};
JSONPath.prototype._eval = function(code, _v, _vname, path, parent, parentPropName) {
this.currSandbox._$_parentProperty = parentPropName;
this.currSandbox._$_parent = parent;
this.currSandbox._$_property = _vname;
this.currSandbox._$_root = this.json;
this.currSandbox._$_v = _v;
const containsPath = code.includes("@path");
if (containsPath) {
this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));
}
const scriptCacheKey = this.currEval + "Script:" + code;
if (!JSONPath.cache[scriptCacheKey]) {
let script = code.replace(/@parentProperty/gu, "_$_parentProperty").replace(/@parent/gu, "_$_parent").replace(/@property/gu, "_$_property").replace(/@root/gu, "_$_root").replace(/@([.\s)[])/gu, "_$_v$1");
if (containsPath) {
script = script.replace(/@path/gu, "_$_path");
}
if (this.currEval === "safe" || this.currEval === true || this.currEval === void 0) {
JSONPath.cache[scriptCacheKey] = new this.safeVm.Script(script);
} else if (this.currEval === "native") {
JSONPath.cache[scriptCacheKey] = new this.vm.Script(script);
} else if (typeof this.currEval === "function" && this.currEval.prototype && hasOwnProp.call(this.currEval.prototype, "runInNewContext")) {
const CurrEval = this.currEval;
JSONPath.cache[scriptCacheKey] = new CurrEval(script);
} else if (typeof this.currEval === "function") {
JSONPath.cache[scriptCacheKey] = {
runInNewContext: (context) => this.currEval(script, context)
};
} else {
throw new TypeError(`Unknown "eval" property "${this.currEval}"`);
}
}
try {
return JSONPath.cache[scriptCacheKey].runInNewContext(this.currSandbox);
} catch (e) {
if (this.ignoreEvalErrors) {
return false;
}
throw new Error("jsonPath: " + e.message + ": " + code);
}
};
JSONPath.cache = {};
JSONPath.toPathString = function(pathArr) {
const x = pathArr, n = x.length;
let p = "$";
for (let i = 1; i < n; i++) {
if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
p += /^[0-9*]+$/u.test(x[i]) ? "[" + x[i] + "]" : "['" + x[i] + "']";
}
}
return p;
};
JSONPath.toPointer = function(pointer) {
const x = pointer, n = x.length;
let p = "";
for (let i = 1; i < n; i++) {
if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
p += "/" + x[i].toString().replace(/~/gu, "~0").replace(/\//gu, "~1");
}
}
return p;
};
JSONPath.toPathArray = function(expr) {
const {
cache
} = JSONPath;
if (cache[expr]) {
return cache[expr].concat();
}
const subx = [];
const normalized = expr.replace(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ";$&;").replace(/[['](\??\(.*?\))[\]'](?!.\])/gu, function($0, $1) {
return "[#" + (subx.push($1) - 1) + "]";
}).replace(/\[['"]([^'\]]*)['"]\]/gu, function($0, prop) {
return "['" + prop.replace(/\./gu, "%@%").replace(/~/gu, "%%@@%%") + "']";
}).replace(/~/gu, ";~;").replace(/['"]?\.['"]?(?![^[]*\])|\[['"]?/gu, ";").replace(/%@%/gu, ".").replace(/%%@@%%/gu, "~").replace(/(?:;)?(\^+)(?:;)?/gu, function($0, ups) {
return ";" + ups.split("").join(";") + ";";
}).replace(/;;;|;;/gu, ";..;").replace(/;$|'?\]|'$/gu, "");
const exprList = normalized.split(";").map(function(exp) {
const match = exp.match(/#(\d+)/u);
return !match || !match[1] ? exp : subx[match[1]];
});
cache[expr] = exprList;
return cache[expr].concat();
};
JSONPath.prototype.vm = import_vm.default;
JSONPath.prototype.safeVm = import_vm.default;
var SafeScript = import_vm.default.Script;
// src/index.ts
function pluginHookResponseFilter(_ctx, args) {
const parsed = JSON.parse(args.body);
const filtered = JSONPath({ path: args.filter, json: parsed });
return JSON.stringify(filtered, null, 2);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookResponseFilter
});

View File

@@ -0,0 +1,15 @@
{
"name": "@yaakapp/filter-jsonpath",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"jsonpath-plus": "^9.0.0"
},
"devDependencies": {
"@types/jsonpath": "^0.2.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "@yaakapp/filter-xpath",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.34"
}
}

View File

@@ -0,0 +1,574 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// ../../node_modules/shell-quote/quote.js
var require_quote = __commonJS({
"../../node_modules/shell-quote/quote.js"(exports2, module2) {
"use strict";
module2.exports = function quote(xs) {
return xs.map(function(s) {
if (s && typeof s === "object") {
return s.op.replace(/(.)/g, "\\$1");
}
if (/["\s]/.test(s) && !/'/.test(s)) {
return "'" + s.replace(/(['\\])/g, "\\$1") + "'";
}
if (/["'\s]/.test(s)) {
return '"' + s.replace(/(["\\$`!])/g, "\\$1") + '"';
}
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, "$1\\$2");
}).join(" ");
};
}
});
// ../../node_modules/shell-quote/parse.js
var require_parse = __commonJS({
"../../node_modules/shell-quote/parse.js"(exports2, module2) {
"use strict";
var CONTROL = "(?:" + [
"\\|\\|",
"\\&\\&",
";;",
"\\|\\&",
"\\<\\(",
"\\<\\<\\<",
">>",
">\\&",
"<\\&",
"[&;()|<>]"
].join("|") + ")";
var controlRE = new RegExp("^" + CONTROL + "$");
var META = "|&;()<> \\t";
var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
var DOUBLE_QUOTE = "'((\\\\'|[^'])*?)'";
var hash = /^#$/;
var SQ = "'";
var DQ = '"';
var DS = "$";
var TOKEN = "";
var mult = 4294967296;
for (i = 0; i < 4; i++) {
TOKEN += (mult * Math.random()).toString(16);
}
var i;
var startsWithToken = new RegExp("^" + TOKEN);
function matchAll(s, r) {
var origIndex = r.lastIndex;
var matches = [];
var matchObj;
while (matchObj = r.exec(s)) {
matches.push(matchObj);
if (r.lastIndex === matchObj.index) {
r.lastIndex += 1;
}
}
r.lastIndex = origIndex;
return matches;
}
function getVar(env, pre, key) {
var r = typeof env === "function" ? env(key) : env[key];
if (typeof r === "undefined" && key != "") {
r = "";
} else if (typeof r === "undefined") {
r = "$";
}
if (typeof r === "object") {
return pre + TOKEN + JSON.stringify(r) + TOKEN;
}
return pre + r;
}
function parseInternal(string, env, opts) {
if (!opts) {
opts = {};
}
var BS = opts.escape || "\\";
var BAREWORD = "(\\" + BS + `['"` + META + `]|[^\\s'"` + META + "])+";
var chunker = new RegExp([
"(" + CONTROL + ")",
// control chars
"(" + BAREWORD + "|" + SINGLE_QUOTE + "|" + DOUBLE_QUOTE + ")+"
].join("|"), "g");
var matches = matchAll(string, chunker);
if (matches.length === 0) {
return [];
}
if (!env) {
env = {};
}
var commented = false;
return matches.map(function(match) {
var s = match[0];
if (!s || commented) {
return void 0;
}
if (controlRE.test(s)) {
return { op: s };
}
var quote = false;
var esc = false;
var out = "";
var isGlob = false;
var i2;
function parseEnvVar() {
i2 += 1;
var varend;
var varname;
var char = s.charAt(i2);
if (char === "{") {
i2 += 1;
if (s.charAt(i2) === "}") {
throw new Error("Bad substitution: " + s.slice(i2 - 2, i2 + 1));
}
varend = s.indexOf("}", i2);
if (varend < 0) {
throw new Error("Bad substitution: " + s.slice(i2));
}
varname = s.slice(i2, varend);
i2 = varend;
} else if (/[*@#?$!_-]/.test(char)) {
varname = char;
i2 += 1;
} else {
var slicedFromI = s.slice(i2);
varend = slicedFromI.match(/[^\w\d_]/);
if (!varend) {
varname = slicedFromI;
i2 = s.length;
} else {
varname = slicedFromI.slice(0, varend.index);
i2 += varend.index - 1;
}
}
return getVar(env, "", varname);
}
for (i2 = 0; i2 < s.length; i2++) {
var c = s.charAt(i2);
isGlob = isGlob || !quote && (c === "*" || c === "?");
if (esc) {
out += c;
esc = false;
} else if (quote) {
if (c === quote) {
quote = false;
} else if (quote == SQ) {
out += c;
} else {
if (c === BS) {
i2 += 1;
c = s.charAt(i2);
if (c === DQ || c === BS || c === DS) {
out += c;
} else {
out += BS + c;
}
} else if (c === DS) {
out += parseEnvVar();
} else {
out += c;
}
}
} else if (c === DQ || c === SQ) {
quote = c;
} else if (controlRE.test(c)) {
return { op: s };
} else if (hash.test(c)) {
commented = true;
var commentObj = { comment: string.slice(match.index + i2 + 1) };
if (out.length) {
return [out, commentObj];
}
return [commentObj];
} else if (c === BS) {
esc = true;
} else if (c === DS) {
out += parseEnvVar();
} else {
out += c;
}
}
if (isGlob) {
return { op: "glob", pattern: out };
}
return out;
}).reduce(function(prev, arg) {
return typeof arg === "undefined" ? prev : prev.concat(arg);
}, []);
}
module2.exports = function parse2(s, env, opts) {
var mapped = parseInternal(s, env, opts);
if (typeof env !== "function") {
return mapped;
}
return mapped.reduce(function(acc, s2) {
if (typeof s2 === "object") {
return acc.concat(s2);
}
var xs = s2.split(RegExp("(" + TOKEN + ".*?" + TOKEN + ")", "g"));
if (xs.length === 1) {
return acc.concat(xs[0]);
}
return acc.concat(xs.filter(Boolean).map(function(x) {
if (startsWithToken.test(x)) {
return JSON.parse(x.split(TOKEN)[1]);
}
return x;
}));
}, []);
};
}
});
// ../../node_modules/shell-quote/index.js
var require_shell_quote = __commonJS({
"../../node_modules/shell-quote/index.js"(exports2) {
"use strict";
exports2.quote = require_quote();
exports2.parse = require_parse();
}
});
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
var import_shell_quote = __toESM(require_shell_quote());
var DATA_FLAGS = ["d", "data", "data-raw", "data-urlencode", "data-binary", "data-ascii"];
var SUPPORTED_FLAGS = [
["cookie", "b"],
["d", "data"],
// Add url encoded data
["data-ascii"],
["data-binary"],
["data-raw"],
["data-urlencode"],
["digest"],
// Apply auth as digest
["form", "F"],
// Add multipart data
["get", "G"],
// Put the post data in the URL
["header", "H"],
["request", "X"],
// Request method
["url"],
// Specify the URL explicitly
["url-query"],
["user", "u"],
// Authentication
DATA_FLAGS
].flatMap((v) => v);
var BOOLEAN_FLAGS = ["G", "get", "digest"];
function pluginHookImport(_ctx, rawData) {
if (!rawData.match(/^\s*curl /)) {
return null;
}
const commands = [];
const normalizedData = rawData.replace(/\ncurl/g, "; curl");
let currentCommand = [];
const parsed = (0, import_shell_quote.parse)(normalizedData);
const normalizedParseEntries = parsed.flatMap((entry) => {
if (typeof entry === "string" && entry.startsWith("-") && !entry.startsWith("--") && entry.length > 2) {
return [entry.slice(0, 2), entry.slice(2)];
}
return entry;
});
for (const parseEntry of normalizedParseEntries) {
if (typeof parseEntry === "string") {
if (parseEntry.startsWith("$")) {
currentCommand.push(parseEntry.slice(1));
} else {
currentCommand.push(parseEntry);
}
continue;
}
if ("comment" in parseEntry) {
continue;
}
const { op } = parseEntry;
if (op === ";") {
commands.push(currentCommand);
currentCommand = [];
continue;
}
if (op?.startsWith("$")) {
const str = op.slice(2, op.length - 1).replace(/\\'/g, "'");
currentCommand.push(str);
continue;
}
if (op === "glob") {
currentCommand.push(parseEntry.pattern);
}
}
commands.push(currentCommand);
const workspace = {
model: "workspace",
id: generateId("workspace"),
name: "Curl Import"
};
const requests = commands.filter((command) => command[0] === "curl").map((v) => importCommand(v, workspace.id));
return {
resources: {
httpRequests: requests,
workspaces: [workspace]
}
};
}
function importCommand(parseEntries, workspaceId) {
const flagsByName = {};
const singletons = [];
for (let i = 1; i < parseEntries.length; i++) {
let parseEntry = parseEntries[i];
if (typeof parseEntry === "string") {
parseEntry = parseEntry.trim();
}
if (typeof parseEntry === "string" && parseEntry.match(/^-{1,2}[\w-]+/)) {
const isSingleDash = parseEntry[0] === "-" && parseEntry[1] !== "-";
let name = parseEntry.replace(/^-{1,2}/, "");
if (!SUPPORTED_FLAGS.includes(name)) {
continue;
}
let value;
const nextEntry = parseEntries[i + 1];
const hasValue = !BOOLEAN_FLAGS.includes(name);
if (isSingleDash && name.length > 1) {
value = name.slice(1);
name = name.slice(0, 1);
} else if (typeof nextEntry === "string" && hasValue && !nextEntry.startsWith("-")) {
value = nextEntry;
i++;
} else {
value = true;
}
flagsByName[name] = flagsByName[name] || [];
flagsByName[name].push(value);
} else if (parseEntry) {
singletons.push(parseEntry);
}
}
let urlParameters;
let url;
const urlArg = getPairValue(flagsByName, singletons[0] || "", ["url"]);
const [baseUrl, search] = splitOnce(urlArg, "?");
urlParameters = search?.split("&").map((p) => {
const v = splitOnce(p, "=");
return { name: decodeURIComponent(v[0] ?? ""), value: decodeURIComponent(v[1] ?? ""), enabled: true };
}) ?? [];
url = baseUrl ?? urlArg;
for (const p of flagsByName["url-query"] ?? []) {
if (typeof p !== "string") {
continue;
}
const [name, value] = p.split("=");
urlParameters.push({
name: name ?? "",
value: value ?? "",
enabled: true
});
}
const [username, password] = getPairValue(flagsByName, "", ["u", "user"]).split(/:(.*)$/);
const isDigest = getPairValue(flagsByName, false, ["digest"]);
const authenticationType = username ? isDigest ? "digest" : "basic" : null;
const authentication = username ? {
username: username.trim(),
password: (password ?? "").trim()
} : {};
const headers = [
...flagsByName["header"] || [],
...flagsByName["H"] || []
].map((header) => {
const [name, value] = header.split(/:(.*)$/);
if (!value) {
return {
name: (name ?? "").trim().replace(/;$/, ""),
value: "",
enabled: true
};
}
return {
name: (name ?? "").trim(),
value: value.trim(),
enabled: true
};
});
const cookieHeaderValue = [
...flagsByName["cookie"] || [],
...flagsByName["b"] || []
].map((str) => {
const name = str.split("=", 1)[0];
const value = str.replace(`${name}=`, "");
return `${name}=${value}`;
}).join("; ");
const existingCookieHeader = headers.find((header) => header.name.toLowerCase() === "cookie");
if (cookieHeaderValue && existingCookieHeader) {
existingCookieHeader.value += `; ${cookieHeaderValue}`;
} else if (cookieHeaderValue) {
headers.push({
name: "Cookie",
value: cookieHeaderValue,
enabled: true
});
}
const dataParameters = pairsToDataParameters(flagsByName);
const contentTypeHeader = headers.find((header) => header.name.toLowerCase() === "content-type");
const mimeType = contentTypeHeader ? contentTypeHeader.value.split(";")[0] : null;
const formDataParams = [
...flagsByName["form"] || [],
...flagsByName["F"] || []
].map((str) => {
const parts = str.split("=");
const name = parts[0] ?? "";
const value = parts[1] ?? "";
const item = {
name,
enabled: true
};
if (value.indexOf("@") === 0) {
item["file"] = value.slice(1);
} else {
item["value"] = value;
}
return item;
});
let body = {};
let bodyType = null;
const bodyAsGET = getPairValue(flagsByName, false, ["G", "get"]);
if (dataParameters.length > 0 && bodyAsGET) {
urlParameters.push(...dataParameters);
} else if (dataParameters.length > 0 && (mimeType == null || mimeType === "application/x-www-form-urlencoded")) {
bodyType = mimeType ?? "application/x-www-form-urlencoded";
body = {
form: dataParameters.map((parameter) => ({
...parameter,
name: decodeURIComponent(parameter.name || ""),
value: decodeURIComponent(parameter.value || "")
}))
};
headers.push({
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: true
});
} else if (dataParameters.length > 0) {
bodyType = mimeType === "application/json" || mimeType === "text/xml" || mimeType === "text/plain" ? mimeType : "other";
body = {
text: dataParameters.map(({ name, value }) => name && value ? `${name}=${value}` : name || value).join("&")
};
} else if (formDataParams.length) {
bodyType = mimeType ?? "multipart/form-data";
body = {
form: formDataParams
};
if (mimeType == null) {
headers.push({
name: "Content-Type",
value: "multipart/form-data",
enabled: true
});
}
}
let method = getPairValue(flagsByName, "", ["X", "request"]).toUpperCase();
if (method === "" && body) {
method = "text" in body || "form" in body ? "POST" : "GET";
}
const request = {
id: generateId("http_request"),
model: "http_request",
workspaceId,
name: "",
urlParameters,
url,
method,
headers,
authentication,
authenticationType,
body,
bodyType,
folderId: null,
sortPriority: 0
};
return request;
}
function pairsToDataParameters(keyedPairs) {
let dataParameters = [];
for (const flagName of DATA_FLAGS) {
const pairs = keyedPairs[flagName];
if (!pairs || pairs.length === 0) {
continue;
}
for (const p of pairs) {
if (typeof p !== "string") continue;
const [name, value] = p.split("=");
if (p.startsWith("@")) {
dataParameters.push({
name: name ?? "",
value: "",
filePath: p.slice(1),
enabled: true
});
} else {
dataParameters.push({
name: name ?? "",
value: flagName === "data-urlencode" ? encodeURIComponent(value ?? "") : value ?? "",
enabled: true
});
}
}
}
return dataParameters;
}
var getPairValue = (pairsByName, defaultValue, names) => {
for (const name of names) {
if (pairsByName[name] && pairsByName[name].length) {
return pairsByName[name][0];
}
}
return defaultValue;
};
function splitOnce(str, sep) {
const index = str.indexOf(sep);
if (index > -1) {
return [str.slice(0, index), str.slice(index + 1)];
}
return [str];
}
var idCount = {};
function generateId(model) {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,15 @@
{
"name": "@yaakapp/importer-curl",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"shell-quote": "^1.8.1"
},
"devDependencies": {
"@types/shell-quote": "^1.7.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "@yaakapp/importer-insomnia",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"yaml": "^2.4.2"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
{
"name": "@yaakapp/importer-openapi",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"openapi-to-postmanv2": "^4.23.1",
"yaml": "^2.4.2"
},
"devDependencies": {
"@types/openapi-to-postmanv2": "^3.2.4"
}
}

View File

@@ -0,0 +1,321 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
var POSTMAN_2_1_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
var POSTMAN_2_0_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json";
var VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA];
function pluginHookImport(_ctx, contents) {
const root = parseJSONToRecord(contents);
if (root == null) return;
const info = toRecord(root.info);
const isValidSchema = VALID_SCHEMAS.includes(info.schema);
if (!isValidSchema || !Array.isArray(root.item)) {
return;
}
const globalAuth = importAuth(root.auth);
const exportResources = {
workspaces: [],
environments: [],
httpRequests: [],
folders: []
};
const workspace = {
model: "workspace",
id: generateId("workspace"),
name: info.name || "Postman Import",
description: info.description?.content ?? info.description
};
exportResources.workspaces.push(workspace);
const environment = {
model: "environment",
id: generateId("environment"),
name: "Global Variables",
workspaceId: workspace.id,
variables: root.variable?.map((v) => ({
name: v.key,
value: v.value
})) ?? []
};
exportResources.environments.push(environment);
const importItem = (v, folderId = null) => {
if (typeof v.name === "string" && Array.isArray(v.item)) {
const folder = {
model: "folder",
workspaceId: workspace.id,
id: generateId("folder"),
name: v.name,
folderId
};
exportResources.folders.push(folder);
for (const child of v.item) {
importItem(child, folder.id);
}
} else if (typeof v.name === "string" && "request" in v) {
const r = toRecord(v.request);
const bodyPatch = importBody(r.body);
const requestAuthPath = importAuth(r.auth);
const authPatch = requestAuthPath.authenticationType == null ? globalAuth : requestAuthPath;
const headers = toArray(r.header).map((h) => {
return {
name: h.key,
value: h.value,
enabled: !h.disabled
};
});
for (const bodyPatchHeader of bodyPatch.headers) {
const existingHeader = headers.find((h) => h.name.toLowerCase() === bodyPatchHeader.name.toLowerCase());
if (existingHeader) {
continue;
}
headers.push(bodyPatchHeader);
}
const { url, urlParameters } = convertUrl(r.url);
const request = {
model: "http_request",
id: generateId("http_request"),
workspaceId: workspace.id,
folderId,
name: v.name,
description: v.description || void 0,
method: r.method || "GET",
url,
urlParameters,
body: bodyPatch.body,
bodyType: bodyPatch.bodyType,
authentication: authPatch.authentication,
authenticationType: authPatch.authenticationType,
headers
};
exportResources.httpRequests.push(request);
} else {
console.log("Unknown item", v, folderId);
}
};
for (const item of root.item) {
importItem(item);
}
const resources = deleteUndefinedAttrs(convertTemplateSyntax(exportResources));
return { resources };
}
function convertUrl(url) {
if (typeof url === "string") {
return { url, urlParameters: [] };
}
url = toRecord(url);
let v = "";
if ("protocol" in url && typeof url.protocol === "string") {
v += `${url.protocol}://`;
}
if ("host" in url) {
v += `${Array.isArray(url.host) ? url.host.join(".") : url.host}`;
}
if ("port" in url && typeof url.port === "string") {
v += `:${url.port}`;
}
if ("path" in url && Array.isArray(url.path) && url.path.length > 0) {
v += `/${Array.isArray(url.path) ? url.path.join("/") : url.path}`;
}
const params = [];
if ("query" in url && Array.isArray(url.query) && url.query.length > 0) {
for (const query of url.query) {
params.push({
name: query.key ?? "",
value: query.value ?? "",
enabled: !query.disabled
});
}
}
if ("variable" in url && Array.isArray(url.variable) && url.variable.length > 0) {
for (const v2 of url.variable) {
params.push({
name: ":" + (v2.key ?? ""),
value: v2.value ?? "",
enabled: !v2.disabled
});
}
}
if ("hash" in url && typeof url.hash === "string") {
v += `#${url.hash}`;
}
return { url: v, urlParameters: params };
}
function importAuth(rawAuth) {
const auth = toRecord(rawAuth);
if ("basic" in auth) {
return {
authenticationType: "basic",
authentication: {
username: auth.basic.username || "",
password: auth.basic.password || ""
}
};
} else if ("bearer" in auth) {
return {
authenticationType: "bearer",
authentication: {
token: auth.bearer.token || ""
}
};
} else {
return { authenticationType: null, authentication: {} };
}
}
function importBody(rawBody) {
const body = toRecord(rawBody);
if (body.mode === "graphql") {
return {
headers: [
{
name: "Content-Type",
value: "application/json",
enabled: true
}
],
bodyType: "graphql",
body: {
text: JSON.stringify(
{ query: body.graphql.query, variables: parseJSONToRecord(body.graphql.variables) },
null,
2
)
}
};
} else if (body.mode === "urlencoded") {
return {
headers: [
{
name: "Content-Type",
value: "application/x-www-form-urlencoded",
enabled: true
}
],
bodyType: "application/x-www-form-urlencoded",
body: {
form: toArray(body.urlencoded).map((f) => ({
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? ""
}))
}
};
} else if (body.mode === "formdata") {
return {
headers: [
{
name: "Content-Type",
value: "multipart/form-data",
enabled: true
}
],
bodyType: "multipart/form-data",
body: {
form: toArray(body.formdata).map(
(f) => f.src != null ? {
enabled: !f.disabled,
contentType: f.contentType ?? null,
name: f.key ?? "",
file: f.src ?? ""
} : {
enabled: !f.disabled,
name: f.key ?? "",
value: f.value ?? ""
}
)
}
};
} else if (body.mode === "raw") {
return {
headers: [
{
name: "Content-Type",
value: body.options?.raw?.language === "json" ? "application/json" : "",
enabled: true
}
],
bodyType: body.options?.raw?.language === "json" ? "application/json" : "other",
body: {
text: body.raw ?? ""
}
};
} else if (body.mode === "file") {
return {
headers: [],
bodyType: "binary",
body: {
filePath: body.file?.src
}
};
} else {
return { headers: [], bodyType: null, body: {} };
}
}
function parseJSONToRecord(jsonStr) {
try {
return toRecord(JSON.parse(jsonStr));
} catch (err) {
}
return null;
}
function toRecord(value) {
if (Object.prototype.toString.call(value) === "[object Object]") return value;
else return {};
}
function toArray(value) {
if (Object.prototype.toString.call(value) === "[object Array]") return value;
else return [];
}
function convertTemplateSyntax(obj) {
if (typeof obj === "string") {
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}");
} else if (Array.isArray(obj) && obj != null) {
return obj.map(convertTemplateSyntax);
} else if (typeof obj === "object" && obj != null) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)])
);
} else {
return obj;
}
}
function deleteUndefinedAttrs(obj) {
if (Array.isArray(obj) && obj != null) {
return obj.map(deleteUndefinedAttrs);
} else if (typeof obj === "object" && obj != null) {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => v !== void 0).map(([k, v]) => [k, deleteUndefinedAttrs(v)])
);
} else {
return obj;
}
}
var idCount = {};
function generateId(model) {
idCount[model] = (idCount[model] ?? -1) + 1;
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,10 @@
{
"name": "@yaakapp/importer-postman",
"private": true,
"version": "0.0.1",
"main": "./build/index.js",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,70 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
pluginHookImport: () => pluginHookImport
});
module.exports = __toCommonJS(src_exports);
function pluginHookImport(_ctx, contents) {
let parsed;
try {
parsed = JSON.parse(contents);
} catch (err) {
return void 0;
}
if (!isJSObject(parsed)) {
return void 0;
}
const isYaakExport = "yaakSchema" in parsed;
if (!isYaakExport) {
return;
}
if ("requests" in parsed.resources) {
parsed.resources.httpRequests = parsed.resources.requests;
delete parsed.resources["requests"];
}
for (const workspace of parsed.resources.workspaces ?? []) {
if ("variables" in workspace) {
const baseEnvironment = {
id: `GENERATE_ID::base_env_${workspace["id"]}`,
name: "Global Variables",
variables: workspace.variables,
workspaceId: workspace.id
};
parsed.resources.environments = parsed.resources.environments ?? [];
parsed.resources.environments.push(baseEnvironment);
delete workspace.variables;
for (const environment of parsed.resources.environments) {
if (environment.workspaceId === workspace.id && environment.id !== baseEnvironment.id) {
environment.environmentId = baseEnvironment.id;
}
}
}
}
return { resources: parsed.resources };
}
function isJSObject(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginHookImport
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/importer-yaak",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.js",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,54 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var import_node_fs = __toESM(require("node:fs"));
var plugin = {
templateFunctions: [{
name: "fs.readFile",
args: [{ title: "Select File", type: "file", name: "path", label: "File" }],
async onRender(_ctx, args) {
if (!args.values.path) return null;
try {
return import_node_fs.default.promises.readFile(args.values.path, "utf-8");
} catch (err) {
return null;
}
}
}]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-file",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,55 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var import_node_fs = __toESM(require("node:fs"));
var plugin = {
templateFunctions: [{
name: "fs.readFile",
description: "Read the contents of a file as utf-8",
args: [{ title: "Select File", type: "file", name: "path", label: "File" }],
async onRender(_ctx, args) {
if (!args.values.path) return null;
try {
return import_node_fs.default.promises.readFile(args.values.path, "utf-8");
} catch (err) {
return null;
}
}
}]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-fs",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,49 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var import_node_crypto = require("node:crypto");
var algorithms = ["md5", "sha1", "sha256", "sha512"];
var plugin = {
templateFunctions: algorithms.map((algorithm) => ({
name: `hash.${algorithm}`,
description: "Hash a value to its hexidecimal representation",
args: [
{
name: "input",
label: "Input",
placeholder: "input text",
type: "text"
}
],
async onRender(_ctx, args) {
if (!args.values.input) return "";
return (0, import_node_crypto.createHash)(algorithm).update(args.values.input, "utf-8").digest("hex");
}
}))
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-hash",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,51 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var plugin = {
templateFunctions: [{
name: "prompt.text",
description: "Prompt the user for input when sending a request",
args: [
{ type: "text", name: "title", label: "Title" },
{ type: "text", name: "label", label: "Label", optional: true },
{ type: "text", name: "defaultValue", label: "Default Value", optional: true },
{ type: "text", name: "placeholder", label: "Placeholder", optional: true }
],
async onRender(ctx, args) {
if (args.purpose !== "send") return null;
return await ctx.prompt.text({
id: `prompt-${args.values.label}`,
label: args.values.title ?? "",
title: args.values.title ?? "",
defaultValue: args.values.defaultValue,
placeholder: args.values.placeholder
});
}
}]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-prompt",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,73 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var plugin = {
templateFunctions: [
{
name: "request.body",
args: [{
name: "requestId",
label: "Http Request",
type: "http_request"
}],
async onRender(ctx, args) {
const httpRequest = await ctx.httpRequest.getById({ id: args.values.requestId ?? "n/a" });
if (httpRequest == null) return null;
return String(await ctx.templates.render({
data: httpRequest.body?.text ?? "",
purpose: args.purpose
}));
}
},
{
name: "request.header",
args: [
{
name: "requestId",
label: "Http Request",
type: "http_request"
},
{
name: "header",
label: "Header Name",
type: "text"
}
],
async onRender(ctx, args) {
const httpRequest = await ctx.httpRequest.getById({ id: args.values.requestId ?? "n/a" });
if (httpRequest == null) return null;
const header = httpRequest.headers.find((h) => h.name.toLowerCase() === args.values.header?.toLowerCase());
return String(await ctx.templates.render({
data: header?.value ?? "",
purpose: args.purpose
}));
}
}
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-request",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"name": "@yaakapp/template-function-response",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"jsonpath-plus": "^9.0.0",
"xpath": "^0.0.34",
"@xmldom/xmldom": "^0.8.10"
},
"devDependencies": {
"@types/jsonpath": "^0.2.4"
}
}

View File

@@ -1,7 +1,8 @@
[package]
name = "yaak_grpc"
name = "yaak-grpc"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
tonic = "0.10.2"
@@ -22,3 +23,4 @@ tauri = { workspace = true }
tauri-plugin-shell = { workspace = true }
md5 = "0.7.0"
dunce = "1.0.4"
async-recursion = "1.1.1"

View File

@@ -33,8 +33,7 @@ impl Encoder for DynamicCodec {
type Error = Status;
fn encode(&mut self, item: Self::Item, dst: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
item.encode(dst)
.expect("buffer is too small to decode this message");
item.encode(dst).expect("buffer is too small to decode this message");
Ok(())
}
}
@@ -45,8 +44,7 @@ impl Decoder for DynamicCodec {
fn decode(&mut self, src: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
let mut msg = DynamicMessage::new(self.0.output());
msg.merge(src)
.map_err(|err| Status::internal(err.to_string()))?;
msg.merge(src).map_err(|err| Status::internal(err.to_string()))?;
Ok(Some(msg))
}
}

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