mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-18 16:47:48 +01:00
Compare commits
745 Commits
v2025.5.0-
...
v2024.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f9a7e9c88 | ||
|
|
60e469a1c9 | ||
|
|
7b4bc53c0f | ||
|
|
c0068a1561 | ||
|
|
fad5e03f8b | ||
|
|
a22fc68764 | ||
|
|
59a442e8c0 | ||
|
|
35e8155ecf | ||
|
|
8b9a5cb5fb | ||
|
|
29f9825f04 | ||
|
|
ad8a6beacf | ||
|
|
194ac78814 | ||
|
|
0542f1ebea | ||
|
|
8de917ea5e | ||
|
|
0b494bbfbf | ||
|
|
7e74f71c79 | ||
|
|
bd7fd676a5 | ||
|
|
cb1c6a4d8c | ||
|
|
b640f0c357 | ||
|
|
967590c7ff | ||
|
|
321d6ec9d7 | ||
|
|
79b4fd7829 | ||
|
|
52eb4d338f | ||
|
|
4b8b48e92f | ||
|
|
e3f45b58e5 | ||
|
|
a5f1922446 | ||
|
|
22e7b15a63 | ||
|
|
c6bb2a73f8 | ||
|
|
bde961f95d | ||
|
|
c4867b3f68 | ||
|
|
1c36e9a2f0 | ||
|
|
1c82d8a738 | ||
|
|
a2df591102 | ||
|
|
c950ee0fb8 | ||
|
|
0ce5a9fcce | ||
|
|
bd1970a805 | ||
|
|
3aaa4b1050 | ||
|
|
31d2426846 | ||
|
|
25c4e4edaf | ||
|
|
1a5bf53b02 | ||
|
|
e2d1b62044 | ||
|
|
6186104d11 | ||
|
|
f1e97eaea4 | ||
|
|
bb0e58bf8b | ||
|
|
dc802fa055 | ||
|
|
3c8e19367f | ||
|
|
a89887eab8 | ||
|
|
7d3f0ad549 | ||
|
|
5608db1334 | ||
|
|
10443b3c02 | ||
|
|
7d272f3cd6 | ||
|
|
d60a62ab24 | ||
|
|
2b0f73b7c5 | ||
|
|
0915ea8585 | ||
|
|
ed9a0b8ac7 | ||
|
|
7437f39ed3 | ||
|
|
27f80cd97b | ||
|
|
04cf688a9b | ||
|
|
178af308ce | ||
|
|
d40732a910 | ||
|
|
d8a579b7b3 | ||
|
|
bf8ef40708 | ||
|
|
41060d5d43 | ||
|
|
ab6cef064c | ||
|
|
829d10d7b9 | ||
|
|
57e1f641a7 | ||
|
|
7df965e74b | ||
|
|
0e1153fdfd | ||
|
|
9797bc1830 | ||
|
|
9d00eb98d2 | ||
|
|
88f6e882c6 | ||
|
|
230a773909 | ||
|
|
e6da1afa82 | ||
|
|
a70d9e57be | ||
|
|
8afe0c0755 | ||
|
|
e47c2513a8 | ||
|
|
276bcfceeb | ||
|
|
6fd1b35a50 | ||
|
|
bc33244549 | ||
|
|
33c982b288 | ||
|
|
98493a1167 | ||
|
|
a8c10f9601 | ||
|
|
1051f84bbf | ||
|
|
eea137e677 | ||
|
|
27cf4e925f | ||
|
|
6b239c4e3f | ||
|
|
4c7ee5ef80 | ||
|
|
3ddb79899e | ||
|
|
b14595a3e2 | ||
|
|
82c94369ae | ||
|
|
7e77d2e9a8 | ||
|
|
ce5ce76b21 | ||
|
|
27d6b30cf9 | ||
|
|
9285dd5d70 | ||
|
|
977234808e | ||
|
|
e1ccd327f5 | ||
|
|
a0e3e71a4b | ||
|
|
2d4037da37 | ||
|
|
d21608bb82 | ||
|
|
f16239de73 | ||
|
|
d00f34c240 | ||
|
|
fd77437f59 | ||
|
|
f9cd8d24a1 | ||
|
|
c0a9f79834 | ||
|
|
ca63c101a1 | ||
|
|
dbd0b46ef2 | ||
|
|
89793ebe2f | ||
|
|
e1ffc387ea | ||
|
|
1efd0852ef | ||
|
|
59d532ed4d | ||
|
|
f034cda7cd | ||
|
|
5c1cf1e57d | ||
|
|
780960f5de | ||
|
|
aada95e9da | ||
|
|
89b5003cce | ||
|
|
3559333461 | ||
|
|
fbb6cab567 | ||
|
|
30fca29c6d | ||
|
|
ec1e521b39 | ||
|
|
a8998cd696 | ||
|
|
df178baed6 | ||
|
|
e2f75371e6 | ||
|
|
9f25ab39c6 | ||
|
|
937df6a024 | ||
|
|
7deb7182a8 | ||
|
|
82b14c56be | ||
|
|
58b98734c9 | ||
|
|
c2f58f328c | ||
|
|
36eabe7528 | ||
|
|
8347093e8b | ||
|
|
75282cb43a | ||
|
|
3a253a758d | ||
|
|
f2955c26c1 | ||
|
|
e6c0317b37 | ||
|
|
0cfb218b07 | ||
|
|
8ef103fbde | ||
|
|
ec5cdfb025 | ||
|
|
7f4d082c17 | ||
|
|
e10011ef34 | ||
|
|
1eb6999c37 | ||
|
|
35596916bf | ||
|
|
d61b22dd87 | ||
|
|
5565a9db9a | ||
|
|
b803655306 | ||
|
|
20df2bf13a | ||
|
|
e8fab85ce5 | ||
|
|
ce730f3dbe | ||
|
|
2328973de5 | ||
|
|
37dea50c91 | ||
|
|
40dfc8b30a | ||
|
|
72971bb9ec | ||
|
|
f835599502 | ||
|
|
0caa4f8099 | ||
|
|
71d3c9acd1 | ||
|
|
239ffa174a | ||
|
|
eae79ab14b | ||
|
|
ef7912615a | ||
|
|
7f20b67380 | ||
|
|
504337c178 | ||
|
|
d6b4f06ac8 | ||
|
|
28cbf5474d | ||
|
|
d797b84d4e | ||
|
|
720745857d | ||
|
|
a74ea1aeda | ||
|
|
643f5e7f26 | ||
|
|
a208b934e4 | ||
|
|
5892774082 | ||
|
|
13bfc1c3bd | ||
|
|
00289734c7 | ||
|
|
09c7c2cb91 | ||
|
|
bbe62abd20 | ||
|
|
cae9a4fd36 | ||
|
|
e7ee4a8867 | ||
|
|
b020574c88 | ||
|
|
192e1da5b6 | ||
|
|
0640079e59 | ||
|
|
a9d99aa17f | ||
|
|
b4667e1f88 | ||
|
|
7bc26fd448 | ||
|
|
94a9a5d5d5 | ||
|
|
bcfa2c411f | ||
|
|
1e3d43dbae | ||
|
|
c6b5e4d5df | ||
|
|
1293870e11 | ||
|
|
8309c19167 | ||
|
|
63a381c55a | ||
|
|
d891f891b7 | ||
|
|
0b12a6b318 | ||
|
|
0144ab05a0 | ||
|
|
1f71d4372f | ||
|
|
5ed1ea07ef | ||
|
|
4d2b101278 | ||
|
|
1dfdadde98 | ||
|
|
d19729869e | ||
|
|
5f782ad109 | ||
|
|
27dbdc9b5a | ||
|
|
4892863dd7 | ||
|
|
dc077209cc | ||
|
|
d8d5344d21 | ||
|
|
d148a8384d | ||
|
|
6884e9428b | ||
|
|
25ebccfcd7 | ||
|
|
7adb0cbb50 | ||
|
|
50866abda4 | ||
|
|
c83d904cf0 | ||
|
|
160447f8f6 | ||
|
|
184b13cc2a | ||
|
|
8fa965e055 | ||
|
|
1dd0b69079 | ||
|
|
be8dd107e3 | ||
|
|
8f139f10ef | ||
|
|
8b0823984b | ||
|
|
d82d2229d4 | ||
|
|
4be1bc17f3 | ||
|
|
e6af0c6009 | ||
|
|
e5d10bd72b | ||
|
|
9426885bb8 | ||
|
|
89e5d4f235 | ||
|
|
eecb3fbc7f | ||
|
|
244f1319b4 | ||
|
|
6a2b76e760 | ||
|
|
bc0278fce9 | ||
|
|
959841fb22 | ||
|
|
fbc878dbe5 | ||
|
|
7d183c6580 | ||
|
|
0555420ad9 | ||
|
|
ae25561c7e | ||
|
|
92e2b2b8f9 | ||
|
|
2a4a830fb7 | ||
|
|
0ad4c7cd7e | ||
|
|
41413d52ad | ||
|
|
eabc1bd305 | ||
|
|
9c312e12c1 | ||
|
|
18ea9dda3d | ||
|
|
56d4212f68 | ||
|
|
d932c19513 | ||
|
|
b800f00b7e | ||
|
|
e43af5234f | ||
|
|
aa59d96e55 | ||
|
|
5be04ceea6 | ||
|
|
da3392ac53 | ||
|
|
a2c9c98b21 | ||
|
|
8298d6e031 | ||
|
|
d6331022ad | ||
|
|
5e75d8c9a7 | ||
|
|
51944a212a | ||
|
|
49ed756479 | ||
|
|
1d207d5fbd | ||
|
|
6b1d15415d | ||
|
|
df3bfaaab7 | ||
|
|
ad0b8a8e7d | ||
|
|
d5459229b9 | ||
|
|
adbf596f0b | ||
|
|
c740966394 | ||
|
|
7adab73af3 | ||
|
|
c964f255d8 | ||
|
|
a7ffed9716 | ||
|
|
488d66d248 | ||
|
|
93cb469cb8 | ||
|
|
03a2fc8ee5 | ||
|
|
2d72e5792e | ||
|
|
9bf9a87f12 | ||
|
|
e2fca399e0 | ||
|
|
e13fdddf98 | ||
|
|
890eea299d | ||
|
|
9beac00981 | ||
|
|
1a64d7d9e6 | ||
|
|
bd5ae12f2e | ||
|
|
dbaf1da3ce | ||
|
|
a03c5df440 | ||
|
|
0776f6a2be | ||
|
|
ac9d050d9e | ||
|
|
b885c358a3 | ||
|
|
84d447973e | ||
|
|
019ec4de20 | ||
|
|
26189067cd | ||
|
|
caf39071af | ||
|
|
08a1223482 | ||
|
|
24bd90745e | ||
|
|
7a72920e66 | ||
|
|
81a8276e2b | ||
|
|
8fb6f51555 | ||
|
|
b0026aff66 | ||
|
|
3ced7f7c18 | ||
|
|
33f6995193 | ||
|
|
8af526682a | ||
|
|
d0d4324957 | ||
|
|
2bcd0e0bbe | ||
|
|
1b99c7e10f | ||
|
|
2831bb61b8 | ||
|
|
320670de2a | ||
|
|
1f39a36f26 | ||
|
|
1f84ba716e | ||
|
|
d034965d9c | ||
|
|
5eb30489e5 | ||
|
|
05458a0753 | ||
|
|
7975ef0699 | ||
|
|
5f810a1b4c | ||
|
|
c4093e79cf | ||
|
|
956f8ed2ea | ||
|
|
7b32f76a1e | ||
|
|
0dd11bc051 | ||
|
|
aa38df28af | ||
|
|
32962a6336 | ||
|
|
b268b72a4a | ||
|
|
eb0c90311b | ||
|
|
1070bf8e8f | ||
|
|
9bdb01987c | ||
|
|
3130fb948a | ||
|
|
e46de9eebd | ||
|
|
4360355c8c | ||
|
|
3baaddba0b | ||
|
|
e77c1c2a46 | ||
|
|
38422d59fd | ||
|
|
c884cedfc2 | ||
|
|
91074a35d8 | ||
|
|
9a02b63a6b | ||
|
|
4470409a24 | ||
|
|
8a978420be | ||
|
|
dfc01d51ca | ||
|
|
758154fa14 | ||
|
|
ef23a85577 | ||
|
|
e4533088ed | ||
|
|
4ffce4a534 | ||
|
|
6b77a62934 | ||
|
|
f603867040 | ||
|
|
b6eb7418aa | ||
|
|
39c97681cf | ||
|
|
2d0f0d8f6b | ||
|
|
209a767c91 | ||
|
|
71f2a724cb | ||
|
|
4b89b95738 | ||
|
|
b535722acd | ||
|
|
a9806a06a2 | ||
|
|
948e19b82f | ||
|
|
cd841fa13a | ||
|
|
3ffcf91abd | ||
|
|
9fd84a3bfc | ||
|
|
627c451cd1 | ||
|
|
e6ee89464a | ||
|
|
136e9f2738 | ||
|
|
d97986e526 | ||
|
|
7661aa9819 | ||
|
|
b7596f3f78 | ||
|
|
4e2231674c | ||
|
|
d36d023a5c | ||
|
|
3d5a7ebe3d | ||
|
|
819384e952 | ||
|
|
2f34c5e821 | ||
|
|
0c89c154ee | ||
|
|
060dce7440 | ||
|
|
91707529bd | ||
|
|
22f182a8eb | ||
|
|
6ccc42dc3f | ||
|
|
655f5a8eed | ||
|
|
cd06a72d6f | ||
|
|
0a5d71ecc2 | ||
|
|
9c214b619c | ||
|
|
6fddb727be | ||
|
|
b5d3b9a803 | ||
|
|
a74c8a94db | ||
|
|
abc6d0ff1e | ||
|
|
afdbcd0a38 | ||
|
|
c31ae805a6 | ||
|
|
7c1afd7fe5 | ||
|
|
ad470a3fd2 | ||
|
|
7755d06bba | ||
|
|
5aed4b79be | ||
|
|
f0e3f29606 | ||
|
|
abbcc525bf | ||
|
|
ef8ade45b1 | ||
|
|
8919d598c2 | ||
|
|
e21e42f5fe | ||
|
|
658aed8a29 | ||
|
|
a666f7d216 | ||
|
|
62429df469 | ||
|
|
7aed699c3f | ||
|
|
a14db0ab74 | ||
|
|
7b67770dc7 | ||
|
|
7766d8439b | ||
|
|
fac0683a71 | ||
|
|
79e04967f5 | ||
|
|
47eb8947f5 | ||
|
|
959c55315c | ||
|
|
b392f0c00f | ||
|
|
c8e674d015 | ||
|
|
90fbb81e1d | ||
|
|
21e58ca644 | ||
|
|
cfb0aa55ea | ||
|
|
9e0c021481 | ||
|
|
4acca8dd06 | ||
|
|
88eea09428 | ||
|
|
90d2743267 | ||
|
|
89945532a0 | ||
|
|
671860e053 | ||
|
|
a87ca6af47 | ||
|
|
b59ea4991c | ||
|
|
b6fd59219f | ||
|
|
38ce7650c1 | ||
|
|
2b21e28096 | ||
|
|
3206651248 | ||
|
|
ba8aa0e218 | ||
|
|
975a001635 | ||
|
|
30e7f7ccfe | ||
|
|
cca8d97d63 | ||
|
|
455e6c3520 | ||
|
|
d05ee3ec16 | ||
|
|
b1b5d08e89 | ||
|
|
6202e59daa | ||
|
|
c4a8603b81 | ||
|
|
5d15d1565c | ||
|
|
045ff558f8 | ||
|
|
504ed583cc | ||
|
|
15087f2d5a | ||
|
|
eb1cd1c14b | ||
|
|
0918d86654 | ||
|
|
d0e2220df7 | ||
|
|
ccb04f0b45 | ||
|
|
54b6e1c7c3 | ||
|
|
7c8acdc956 | ||
|
|
3973ae15be | ||
|
|
86dadf4f5e | ||
|
|
dc4cb4be74 | ||
|
|
2a29c4b551 | ||
|
|
2f64f45aba | ||
|
|
2f998ddfb6 | ||
|
|
93369a779d | ||
|
|
74e28123a8 | ||
|
|
fef7db8710 | ||
|
|
cb5d7626ac | ||
|
|
e74f9f33c0 | ||
|
|
0d27c17e28 | ||
|
|
f0b6d32639 | ||
|
|
9ebd506056 | ||
|
|
a5a91d2444 | ||
|
|
4435a66ece | ||
|
|
874a1079c3 | ||
|
|
1dc239d243 | ||
|
|
73a04276c0 | ||
|
|
9955064484 | ||
|
|
7975b528ec | ||
|
|
7d9fbda975 | ||
|
|
fce06747e3 | ||
|
|
db0cca54a7 | ||
|
|
4424caecc5 | ||
|
|
b866dcd566 | ||
|
|
9e4e6435ab | ||
|
|
41b10ff442 | ||
|
|
79f3307104 | ||
|
|
026629cd6d | ||
|
|
cf570a8f88 | ||
|
|
4a64384468 | ||
|
|
a9065c3380 | ||
|
|
e5e5548562 | ||
|
|
44cf2f670f | ||
|
|
d04d91d6dd | ||
|
|
3eb25b1507 | ||
|
|
a95cae5610 | ||
|
|
9cc6e62f28 | ||
|
|
24117f7c8d | ||
|
|
7245e6e593 | ||
|
|
4a5b1f4da3 | ||
|
|
c1ac67cc31 | ||
|
|
4085361b7e | ||
|
|
8b4227dbff | ||
|
|
3f7ed9e177 | ||
|
|
f9f1ba9e24 | ||
|
|
29309500a6 | ||
|
|
6b07fe105f | ||
|
|
90b03a0b97 | ||
|
|
d54a468006 | ||
|
|
7e98b6d853 | ||
|
|
8069094201 | ||
|
|
b28dc01e6b | ||
|
|
6bc1f9f494 | ||
|
|
e85dd32005 | ||
|
|
efc4e3bf6c | ||
|
|
768a13ff4d | ||
|
|
a8d73f74f4 | ||
|
|
4dddfc0cc5 | ||
|
|
4163bebe3b | ||
|
|
ad13744d14 | ||
|
|
9f9b3a5b21 | ||
|
|
63e68baeb1 | ||
|
|
4382ca6582 | ||
|
|
99f2f4c211 | ||
|
|
6b23379e5a | ||
|
|
4deda36e8f | ||
|
|
0ce7831cfb | ||
|
|
4e9005e240 | ||
|
|
9147252e5b | ||
|
|
b6b549ca18 | ||
|
|
85c25fb71e | ||
|
|
50637ba9fd | ||
|
|
bfe55dd55a | ||
|
|
1a2bb3d12e | ||
|
|
452a0c3ed5 | ||
|
|
b594a4690f | ||
|
|
7d2ba43463 | ||
|
|
107db42c33 | ||
|
|
dbacb9fc8d | ||
|
|
1fa3499ca6 | ||
|
|
62f3198d27 | ||
|
|
529550934b | ||
|
|
ca3b22a881 | ||
|
|
0ecf2d9123 | ||
|
|
af9bb7138b | ||
|
|
6cbfa74f97 | ||
|
|
2845fb8d35 | ||
|
|
fbe2660a57 | ||
|
|
1519282ac6 | ||
|
|
5d8d8dca70 | ||
|
|
0254e2c31d | ||
|
|
368b494d62 | ||
|
|
00d2213d05 | ||
|
|
b04602bcb9 | ||
|
|
80bfbd503a | ||
|
|
bfa186aebb | ||
|
|
818595e961 | ||
|
|
b5e9852f8d | ||
|
|
ff3734d65a | ||
|
|
4aa771ba29 | ||
|
|
5848c381fa | ||
|
|
03b35beae4 | ||
|
|
49856bb6f7 | ||
|
|
0cb56f1a85 | ||
|
|
4a0d698776 | ||
|
|
a4ebdb5736 | ||
|
|
6a5ecc2880 | ||
|
|
7a5bd92442 | ||
|
|
63c1111608 | ||
|
|
5704fb560a | ||
|
|
33dc3b719d | ||
|
|
d3329b4628 | ||
|
|
42e2c9f96f | ||
|
|
3e8a10757f | ||
|
|
8028d82fd0 | ||
|
|
4ad9feba68 | ||
|
|
ef469be7a9 | ||
|
|
14cd73d75a | ||
|
|
fbe6039845 | ||
|
|
f3fbd070dd | ||
|
|
7c2611a5a7 | ||
|
|
ae949f4616 | ||
|
|
89da434c0e | ||
|
|
4a98d1d655 | ||
|
|
bb41f0e4fe | ||
|
|
d2e0717d91 | ||
|
|
7912204fcb | ||
|
|
83e41ad618 | ||
|
|
dd7e46c2cc | ||
|
|
fb7424714a | ||
|
|
47481b711e | ||
|
|
0f86c3a731 | ||
|
|
b91d1b8b3c | ||
|
|
cd5ae6691c | ||
|
|
0f58986b4c | ||
|
|
af9755c513 | ||
|
|
56ce25f953 | ||
|
|
ed70c15ee9 | ||
|
|
d88ae99425 | ||
|
|
cf7ef55b7d | ||
|
|
2f12424f8d | ||
|
|
96aacec4fc | ||
|
|
7e57bb98a8 | ||
|
|
298f5c5a99 | ||
|
|
bf44ea7864 | ||
|
|
9abdc45e93 | ||
|
|
0d82cc7574 | ||
|
|
402b2a551f | ||
|
|
700c589ae2 | ||
|
|
8929d736d9 | ||
|
|
8c65fce357 | ||
|
|
b81f1e9e6b | ||
|
|
44d083d773 | ||
|
|
2b308282d4 | ||
|
|
c7738743c5 | ||
|
|
abc60667c6 | ||
|
|
53162e8bca | ||
|
|
6b95574e3d | ||
|
|
c9d62ae961 | ||
|
|
a70019927d | ||
|
|
01cd7f951a | ||
|
|
50f92bcfab | ||
|
|
362d9f8e59 | ||
|
|
0b1cf53942 | ||
|
|
a4c769b33c | ||
|
|
74a1cb61c1 | ||
|
|
268545c728 | ||
|
|
184bbb01c5 | ||
|
|
66fa7ac419 | ||
|
|
a80f3d997e | ||
|
|
efa7c24c9f | ||
|
|
91de21c7ad | ||
|
|
1e9ba57ef0 | ||
|
|
464389b248 | ||
|
|
107eb72eda | ||
|
|
ee7bf838f4 | ||
|
|
f63bcd94d1 | ||
|
|
16d5cb6ade | ||
|
|
63decdef8b | ||
|
|
e54e88f46d | ||
|
|
b6f53d059e | ||
|
|
9e1771c5ec | ||
|
|
8306bc2198 | ||
|
|
3810fb7d51 | ||
|
|
5b4984113e | ||
|
|
baf9efe246 | ||
|
|
f4a3109a31 | ||
|
|
241f2f39ec | ||
|
|
d9b40dca83 | ||
|
|
cb3f053057 | ||
|
|
c4ab045e57 | ||
|
|
d9b38efd97 | ||
|
|
5981588c95 | ||
|
|
388bef59b8 | ||
|
|
3d8de61c1c | ||
|
|
91b818f98d | ||
|
|
3a7f0898f9 | ||
|
|
c2f6de875a | ||
|
|
0647001807 | ||
|
|
4181d87792 | ||
|
|
58cf0a2015 | ||
|
|
d80c3d305b | ||
|
|
4d64a2bc2f | ||
|
|
5c54beaaa9 | ||
|
|
eee98f32b2 | ||
|
|
0949de66bf | ||
|
|
5a6acb24d9 | ||
|
|
2e5cab62c7 | ||
|
|
903db5fffd | ||
|
|
e3faf32708 | ||
|
|
97926ddc03 | ||
|
|
e891804051 | ||
|
|
a2982f8b77 | ||
|
|
321941baab | ||
|
|
e2e25dc30b | ||
|
|
1170ca4789 | ||
|
|
d159f62138 | ||
|
|
25005eef1b | ||
|
|
b17824c88d | ||
|
|
00f4a008f8 | ||
|
|
3e2bc67b59 | ||
|
|
efe072c7c4 | ||
|
|
59f1d11e40 | ||
|
|
5f947ac983 | ||
|
|
aa66f957f2 | ||
|
|
cf5f69271f | ||
|
|
c6653af782 | ||
|
|
fa1f33a2ac | ||
|
|
9f479882ad | ||
|
|
41db316489 | ||
|
|
50f0f5885e | ||
|
|
a609f09d50 | ||
|
|
43f2aa3068 | ||
|
|
a094e13bd5 | ||
|
|
eb076afbe4 | ||
|
|
7b41488a38 | ||
|
|
ca12d48352 | ||
|
|
0331d3b2b0 | ||
|
|
4d4814583c | ||
|
|
da3e158516 | ||
|
|
7b9d6baff0 | ||
|
|
550f1b7c6f | ||
|
|
74e8ee1786 | ||
|
|
55d3ea01b6 | ||
|
|
4cfe51cbb2 | ||
|
|
1f5ac60523 | ||
|
|
26bf4f2abd | ||
|
|
54671fff79 | ||
|
|
378309d763 | ||
|
|
e7d4bba8b3 | ||
|
|
fe3de0bc98 | ||
|
|
f3e38d7b71 | ||
|
|
eafa3b2de9 | ||
|
|
d8cc075bd0 | ||
|
|
bdb877a936 | ||
|
|
5c96e83a22 | ||
|
|
43abb57f77 | ||
|
|
9c6d821978 | ||
|
|
7e9babf515 | ||
|
|
ee36baf432 | ||
|
|
0bf57dcab7 | ||
|
|
e647d23adc | ||
|
|
d1b5b9c371 | ||
|
|
92ec514442 | ||
|
|
107466dd58 | ||
|
|
4246260ce6 | ||
|
|
936787d327 | ||
|
|
f976397283 | ||
|
|
657c6ad9a9 | ||
|
|
ede07c3b0e | ||
|
|
9326e8dcce | ||
|
|
8ff2adf833 | ||
|
|
5e387b513a | ||
|
|
dca316c0d5 | ||
|
|
db2d786d50 | ||
|
|
c0d7962142 | ||
|
|
5d14354ca9 | ||
|
|
347dace6de | ||
|
|
d952c75e3c | ||
|
|
abc3745be1 | ||
|
|
1382d7c523 | ||
|
|
3de0edf0f9 | ||
|
|
5513d39152 | ||
|
|
1a9547d1d2 | ||
|
|
26cc64d3a0 | ||
|
|
e465b33365 | ||
|
|
957739ba5e | ||
|
|
59967374c5 | ||
|
|
43bc346a2b | ||
|
|
5fbd3f67cd | ||
|
|
e352343d62 | ||
|
|
baee0f0c6f | ||
|
|
fcc5eead88 | ||
|
|
29d1f687d1 | ||
|
|
f568266c7f | ||
|
|
a1e42b8ddb | ||
|
|
e6389b1153 | ||
|
|
f0835acb33 | ||
|
|
35b04b219f | ||
|
|
01b62e936a | ||
|
|
09d16a03ef | ||
|
|
bb61602fd2 | ||
|
|
f30b78ea1f | ||
|
|
6ee652ca75 | ||
|
|
67d8bbc154 | ||
|
|
b852484559 | ||
|
|
0b077e5e88 | ||
|
|
381d957db2 | ||
|
|
f8f77abc12 | ||
|
|
0b0484c610 | ||
|
|
2b769088af | ||
|
|
856d13c603 | ||
|
|
76e398b8a1 | ||
|
|
24a7d85be0 | ||
|
|
989271f653 | ||
|
|
3fd8cd5713 | ||
|
|
43f1c7caf6 | ||
|
|
bd91ac88a3 | ||
|
|
fbcbf2e5a5 | ||
|
|
31eb03da0d | ||
|
|
a4dbfed712 | ||
|
|
784cb53ec6 | ||
|
|
43c799bd60 |
5
.eslintignore
Normal file
5
.eslintignore
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.prettierrc.cjs
|
||||
.eslintrc.cjs
|
||||
env.d.ts
|
||||
37
.eslintrc.cjs
Normal file
37
.eslintrc.cjs
Normal file
@@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint-config-prettier"
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"]
|
||||
},
|
||||
ignorePatterns: ["src-tauri/**/*", "plugins/**/*"],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
},
|
||||
"import/resolver": {
|
||||
node: {
|
||||
paths: ["src-web"],
|
||||
extensions: [".ts", ".tsx"]
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"@typescript-eslint/consistent-type-imports": ["error", {
|
||||
prefer: "type-imports",
|
||||
disallowTypeAnnotations: true,
|
||||
fixStyle: "separate-type-imports"
|
||||
}]
|
||||
}
|
||||
};
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
src-tauri/vendored/**/* linguist-generated=true
|
||||
src-tauri/gen/schemas/**/* linguist-generated=true
|
||||
15
.github/FUNDING.yml
vendored
15
.github/FUNDING.yml
vendored
@@ -1,15 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gschier
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: https://yaak.app/pricing
|
||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Bugs, Feedback, Feature Requests, and Questions
|
||||
url: https://feedback.yaak.app
|
||||
about: "Please report to Yaak's public feedback board. Issues will be created and linked here when applicable."
|
||||
18
.github/workflows/ci-js.yml
vendored
18
.github/workflows/ci-js.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
|
||||
name: CI (JS)
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
36
.github/workflows/ci-rust.yml
vendored
36
.github/workflows/ci-rust.yml
vendored
@@ -1,36 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
branches: [develop]
|
||||
paths:
|
||||
- src-tauri/**
|
||||
- .github/workflows/**
|
||||
|
||||
name: CI (Rust)
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src-tauri
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Check/Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
- run: cargo check --all
|
||||
- run: cargo test --all
|
||||
88
.github/workflows/release.yml
vendored
88
.github/workflows/release.yml
vendored
@@ -1,113 +1,65 @@
|
||||
name: Generate Artifacts
|
||||
on:
|
||||
push:
|
||||
tags: [ v* ]
|
||||
|
||||
branches:
|
||||
- release
|
||||
- beta
|
||||
jobs:
|
||||
build-artifacts:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
name: Build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: 'macos-latest' # for Arm-based Macs (M1 and above).
|
||||
- platform: 'macos-latest' # for Arm based macs (M1 and above).
|
||||
args: '--target aarch64-apple-darwin'
|
||||
yaak_arch: 'arm64'
|
||||
- platform: 'macos-latest' # for Intel-based Macs.
|
||||
- platform: 'macos-latest' # for Intel based macs.
|
||||
args: '--target x86_64-apple-darwin'
|
||||
yaak_arch: 'x64'
|
||||
- platform: 'ubuntu-22.04'
|
||||
- platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
||||
args: ''
|
||||
yaak_arch: 'x64'
|
||||
- platform: 'windows-latest'
|
||||
args: ''
|
||||
yaak_arch: 'x64'
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Checkout yaakapp/app
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
node-version: lts/*
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
continue-on-error: false
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
src-tauri/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: ${{ runner.os }}-cargo-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install dependencies (windows only)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: cargo install --force trusted-signing-cli --version 0.5.0
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- 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
|
||||
|
||||
- name: Set version
|
||||
run: npm run replace-version
|
||||
env:
|
||||
YAAK_VERSION: ${{ github.ref_name }}
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
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 }}
|
||||
|
||||
# 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 }}
|
||||
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 }}
|
||||
with:
|
||||
tagName: 'v__VERSION__'
|
||||
releaseName: 'Release __VERSION__'
|
||||
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
|
||||
releaseBody: 'https://yaak.app/changelog/__VERSION__'
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -27,7 +27,4 @@ dist-ssr
|
||||
*.sqlite
|
||||
*.sqlite-*
|
||||
|
||||
.cargo
|
||||
|
||||
.tmp
|
||||
tmp
|
||||
.cargo
|
||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
8
.prettierrc.cjs
Normal file
8
.prettierrc.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true
|
||||
}
|
||||
13
.run/Dev Desktop.run.xml
Normal file
13
.run/Dev Desktop.run.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Dev Desktop" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="start" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs>
|
||||
</envs>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
5
.sqllsrc.json
Normal file
5
.sqllsrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "yaak-dev",
|
||||
"adapter": "sqlite3",
|
||||
"filename": "src-tauri/db.sqlite"
|
||||
}
|
||||
1
.tauriignore
Normal file
1
.tauriignore
Normal file
@@ -0,0 +1 @@
|
||||
plugins
|
||||
@@ -1,62 +0,0 @@
|
||||
# Developer Setup
|
||||
|
||||
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)
|
||||
|
||||
Check the installations with the following commands:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Run the App
|
||||
|
||||
After bootstrapping, start the app in development mode:
|
||||
|
||||
```shell
|
||||
npm start
|
||||
```
|
||||
|
||||
## SQLite Migrations
|
||||
|
||||
New migrations can be created from the `src-tauri/` directory:
|
||||
|
||||
```shell
|
||||
npm run migration
|
||||
```
|
||||
|
||||
Rerun the app to apply the migrations.
|
||||
|
||||
_Note: For safety, development builds use a separate database location from production builds._
|
||||
|
||||
## Lezer Grammer Generation
|
||||
|
||||
```sh
|
||||
# Example
|
||||
lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts
|
||||
```
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Yaak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
13
Makefile
Normal file
13
Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
.PHONY: sqlx-prepare, dev, migrate, build
|
||||
|
||||
sqlx-prepare:
|
||||
cd src-tauri && cargo sqlx prepare --database-url 'sqlite://db.sqlite'
|
||||
|
||||
dev:
|
||||
npm run tauri-dev
|
||||
|
||||
migrate:
|
||||
cd src-tauri && cargo sqlx migrate run --database-url 'sqlite://db.sqlite?mode=rw'
|
||||
|
||||
build:
|
||||
./node_modules/.bin/tauri build
|
||||
42
README.md
42
README.md
@@ -1,34 +1,16 @@
|
||||
# Yaak API Client
|
||||
# Yaak Network Toolkit
|
||||
|
||||
Yaak is a desktop API client for interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC
|
||||
APIs. It's built using [Tauri](https://tauri.app), Rust, and ReactJS.
|
||||
The most fun you'll ever have working with APIs.
|
||||
|
||||

|
||||
## Common Commands
|
||||
|
||||
## Contribution Policy
|
||||
```sh
|
||||
# Start dev app
|
||||
npm run tauri-dev
|
||||
|
||||
Yaak is open source, but only accepting contributions for bug fixes. To get started,
|
||||
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- 🪂 Import data from Postman, Insomnia, OpenAPI, Swagger, or Curl.<br/>
|
||||
- 📤 Send requests via REST, GraphQL, Server Sent Events (SSE), WebSockets, or gRPC.<br/>
|
||||
- 🔐 Automatically authorize requests with OAuth 2.0, JWT tokens, Basic Auth, and more.<br/>
|
||||
- 🔎 Filter response bodies using JSONPath or XPath queries.<br/>
|
||||
- ⛓️ Chain together multiple requests to dynamically reference values.<br/>
|
||||
- 📂 Organize requests into workspaces and nested folders.<br/>
|
||||
- 🧮 Use environment variables to easily switch between Prod and Dev.<br/>
|
||||
- 🛡️ Secure arbitrary text values with end-to-end encryption<br/>
|
||||
- 🏷️ Send dynamic values like UUIDs or timestamps using template tags.<br/>
|
||||
- 🎨 Choose from many of the included themes, or make your own.<br/>
|
||||
- 💽 Mirror workspace data to a directory for integration with Git or Dropbox.<br/>
|
||||
- 📜 View response history for each request.<br/>
|
||||
- 🔌 Create your own plugins for authentication, template tags, and more!<br/>
|
||||
- 🛜 Configure a proxy to access firewall-blocked APIs
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- [Feedback and Bug Reports](https://feedback.yaak.app)
|
||||
- [Documentation](https://feedback.yaak.app/help)
|
||||
- [Yaak vs Postman](https://yaak.app/blog/postman-alternative)
|
||||
# Migration commands
|
||||
cd src-tauri
|
||||
cargo sqlx migrate add ${MIGRATION_NAME}
|
||||
cargo sqlx migrate run --database-url 'sqlite://db.sqlite?mode=rw'
|
||||
cargo sqlx prepare --database-url 'sqlite://db.sqlite'
|
||||
```
|
||||
|
||||
BIN
design/Icons.afdesign
Normal file
BIN
design/Icons.afdesign
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB |
BIN
design/logo.afdesign
Normal file
BIN
design/logo.afdesign
Normal file
Binary file not shown.
@@ -1,89 +0,0 @@
|
||||
const { defineConfig, globalIgnores } = require('eslint/config');
|
||||
|
||||
const { fixupConfigRules } = require('@eslint/compat');
|
||||
|
||||
const reactRefresh = require('eslint-plugin-react-refresh');
|
||||
const tsParser = require('@typescript-eslint/parser');
|
||||
const js = require('@eslint/js');
|
||||
|
||||
const { FlatCompat } = require('@eslint/eslintrc');
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
extends: fixupConfigRules(
|
||||
compat.extends(
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'eslint-config-prettier',
|
||||
),
|
||||
),
|
||||
|
||||
plugins: {
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['src-web'],
|
||||
extensions: ['.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'error',
|
||||
'jsx-a11y/no-autofocus': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
prefer: 'type-imports',
|
||||
disallowTypeAnnotations: true,
|
||||
fixStyle: 'separate-type-imports',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
globalIgnores([
|
||||
'scripts/**/*',
|
||||
'packages/plugin-runtime/**/*',
|
||||
'packages/plugin-runtime-types/**/*',
|
||||
'src-tauri/**/*',
|
||||
'src-web/tailwind.config.cjs',
|
||||
'src-web/vite.config.ts',
|
||||
]),
|
||||
globalIgnores([
|
||||
'**/node_modules/',
|
||||
'**/dist/',
|
||||
'**/build/',
|
||||
'**/.eslintrc.cjs',
|
||||
'**/.prettierrc.cjs',
|
||||
'src-web/postcss.config.cjs',
|
||||
'src-web/vite.config.ts',
|
||||
]),
|
||||
]);
|
||||
@@ -4,20 +4,20 @@
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Yaak App</title>
|
||||
<!-- <script src="http://localhost:8097"></script>-->
|
||||
<!-- <script src="http://localhost:8097"></script>-->
|
||||
|
||||
<!-- Certain elements like webview (and maybe <select>?) will use background
|
||||
color depending on document background color-->
|
||||
<style>
|
||||
html, body {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html, body {
|
||||
background-color: #1b1a29;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html, body {
|
||||
background-color: #1b1a29;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
<div id="cm-portal" class="cm-portal"></div>
|
||||
<div id="react-portal"></div>
|
||||
<div id="radix-portal" class="cm-portal"></div>
|
||||
<script type="module" src="/theme.ts"></script>
|
||||
<script type="module" src="/font-size.ts"></script>
|
||||
<script type="module" src="/font.ts"></script>
|
||||
<script type="module" src="/main.tsx"></script>
|
||||
<script type="module" src="/src-web/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
16115
package-lock.json
generated
16115
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
180
package.json
180
package.json
@@ -2,91 +2,113 @@
|
||||
"name": "yaak-app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/mountain-loop/yaak.git"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
"packages/common-lib",
|
||||
"plugins/auth-basic",
|
||||
"plugins/auth-bearer",
|
||||
"plugins/auth-jwt",
|
||||
"plugins/auth-oauth2",
|
||||
"plugins/action-copy-curl",
|
||||
"plugins/action-copy-grpcurl",
|
||||
"plugins/filter-jsonpath",
|
||||
"plugins/filter-xpath",
|
||||
"plugins/importer-curl",
|
||||
"plugins/importer-insomnia",
|
||||
"plugins/importer-openapi",
|
||||
"plugins/importer-postman",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-encode",
|
||||
"plugins/template-function-fs",
|
||||
"plugins/template-function-hash",
|
||||
"plugins/template-function-json",
|
||||
"plugins/template-function-prompt",
|
||||
"plugins/template-function-regex",
|
||||
"plugins/template-function-request",
|
||||
"plugins/template-function-response",
|
||||
"plugins/template-function-uuid",
|
||||
"plugins/template-function-xml",
|
||||
"plugins/themes-yaak",
|
||||
"src-tauri/yaak-crypto",
|
||||
"src-tauri/yaak-git",
|
||||
"src-tauri/yaak-fonts",
|
||||
"src-tauri/yaak-license",
|
||||
"src-tauri/yaak-mac-window",
|
||||
"src-tauri/yaak-models",
|
||||
"src-tauri/yaak-plugins",
|
||||
"src-tauri/yaak-sse",
|
||||
"src-tauri/yaak-sync",
|
||||
"src-tauri/yaak-templates",
|
||||
"src-tauri/yaak-ws",
|
||||
"src-web"
|
||||
],
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "npm run app-dev",
|
||||
"app-build": "tauri build",
|
||||
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
|
||||
"migration": "node scripts/create-migration.cjs",
|
||||
"build": "npm run --workspaces --if-present build",
|
||||
"build-plugins": "npm run --workspaces --if-present build",
|
||||
"bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
|
||||
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
|
||||
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||
"lint": "npm run --workspaces --if-present lint",
|
||||
"replace-version": "node scripts/replace-version.cjs",
|
||||
"start": "npm run tauri-dev:desktop",
|
||||
"tauri-dev:desktop": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
|
||||
"tauri-dev:ios": "tauri ios dev --force-ip-prompt --config ./src-tauri/tauri-dev.conf.json",
|
||||
"tauri-build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"tauri-before-build": "npm run bootstrap && npm run --workspaces --if-present build",
|
||||
"tauri-before-dev": "workspaces-run --parallel -- npm run --workspaces --if-present dev"
|
||||
"build": "npm run build:frontend",
|
||||
"dev": "vite dev",
|
||||
"lint": "tsc && eslint . --ext .ts,.tsx",
|
||||
"build:icon:release": "tauri icon design/icon.png --output ./src-tauri/icons/release",
|
||||
"build:icon:dev": "tauri icon design/icon-dev.png --output ./src-tauri/icons/dev",
|
||||
"build:frontend": "vite build",
|
||||
"build:plugins": "run-p build:plugin:*",
|
||||
"build:plugin:exporter-curl": "cd plugins/exporter-curl && vite build --emptyOutDir",
|
||||
"build:plugin:importer-insomnia": "cd plugins/importer-insomnia && vite build --emptyOutDir",
|
||||
"build:plugin:importer-postman": "cd plugins/importer-postman && vite build --emptyOutDir",
|
||||
"build:plugin:importer-yaak": "cd plugins/importer-yaak && vite build --emptyOutDir",
|
||||
"build:plugin:importer-curl": "cd plugins/importer-curl && vite build --emptyOutDir",
|
||||
"build:plugin:filter-jsonpath": "cd plugins/filter-jsonpath && vite build --emptyOutDir",
|
||||
"build:plugin:filter-xpath": "cd plugins/filter-xpath && vite build --emptyOutDir",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jotai": "^2.12.2"
|
||||
"@codemirror/commands": "^6.2.1",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.2",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/search": "^6.2.3",
|
||||
"@lezer/generator": "^1.2.2",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.3",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@tailwindcss/container-queries": "^0.1.0",
|
||||
"@tanstack/react-query": "^5.35.5",
|
||||
"@tauri-apps/api": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.1.0-beta.1",
|
||||
"@tauri-apps/plugin-dialog": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-fs": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-os": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/plugin-shell": ">=2.0.0-beta.0",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.9",
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"focus-trap-react": "^10.1.1",
|
||||
"format-graphql": "^1.4.0",
|
||||
"framer-motion": "^9.0.4",
|
||||
"lucide-react": "^0.309.0",
|
||||
"mime": "^4.0.1",
|
||||
"papaparse": "^5.4.1",
|
||||
"parse-color": "^1.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-use": "^17.4.0",
|
||||
"slugify": "^1.6.6",
|
||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v2",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-formatter": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/cli": "2.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||
"@typescript-eslint/parser": "^8.27.0",
|
||||
"@yaakapp/cli": "^0.2.7",
|
||||
"eslint": "^9.29.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||
"@tanstack/react-query-devtools": "^5.35.5",
|
||||
"@tauri-apps/cli": "^2.0.0-beta.15",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/parse-color": "^1.0.1",
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"@types/react": "^18.0.31",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.34.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"husky": "^8.0.3",
|
||||
"internal-ip": "^8.0.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4",
|
||||
"workspaces-run": "^1.0.2"
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-nesting": "^11.2.1",
|
||||
"prettier": "^2.8.4",
|
||||
"react-devtools": "^4.27.2",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-plugin-top-level-await": "^1.4.1",
|
||||
"vitest": "^1.3.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --cache --fix",
|
||||
"*.{js,css,md}": "prettier --write"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export function formatSize(bytes: number): string {
|
||||
let num;
|
||||
let unit;
|
||||
|
||||
if (bytes > 1000 * 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000 / 1000;
|
||||
unit = 'GB';
|
||||
} else if (bytes > 1000 * 1000) {
|
||||
num = bytes / 1000 / 1000;
|
||||
unit = 'MB';
|
||||
} else if (bytes > 1000) {
|
||||
num = bytes / 1000;
|
||||
unit = 'KB';
|
||||
} else {
|
||||
num = bytes;
|
||||
unit = 'B';
|
||||
}
|
||||
|
||||
return `${Math.round(num * 10) / 10} ${unit}`;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './debounce';
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/lib",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts"
|
||||
}
|
||||
2
packages/plugin-runtime-types/.gitignore
vendored
2
packages/plugin-runtime-types/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -1,28 +0,0 @@
|
||||
# Yaak Plugin API
|
||||
|
||||
Yaak is a desktop [API client](https://yaak.app/blog/yet-another-api-client) for
|
||||
interacting with REST, GraphQL, Server Sent Events (SSE), WebSocket, and gRPC APIs. It's
|
||||
built using Tauri, Rust, and ReactJS.
|
||||
|
||||
Plugins can be created in TypeScript, which are executed alongside Yaak in a NodeJS
|
||||
runtime. This package contains the TypeScript type definitions required to make building
|
||||
Yaak plugins a breeze.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The easiest way to get started is by generating a plugin with the Yaak CLI:
|
||||
|
||||
```shell
|
||||
npx @yaakapp/cli generate
|
||||
```
|
||||
|
||||
For more details on creating plugins, check out
|
||||
the [Quick Start Guide](https://feedback.yaak.app/help/articles/6911763-plugins-quick-start)
|
||||
|
||||
## Installation
|
||||
|
||||
If you prefer starting from scratch, manually install the types package:
|
||||
|
||||
```shell
|
||||
npm install -D @yaakapp/api
|
||||
```
|
||||
47
packages/plugin-runtime-types/package-lock.json
generated
47
packages/plugin-runtime-types/package-lock.json
generated
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.17",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.6.6",
|
||||
"keywords": [
|
||||
"api-client",
|
||||
"insomnia-alternative",
|
||||
"bruno-alternative",
|
||||
"postman-alternative"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://feedback.yaak.app"
|
||||
},
|
||||
"homepage": "https://yaak.app",
|
||||
"main": "lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"bootstrap": "npm run build",
|
||||
"build": "run-s build:copy-types build:tsc",
|
||||
"build:tsc": "tsc",
|
||||
"build:copy-types": "run-p build:copy-types:*",
|
||||
"build:copy-types:root": "cpy --flat ../../src-tauri/yaak-plugins/bindings/*.ts ./src/bindings",
|
||||
"build:copy-types:next": "cpy --flat ../../src-tauri/yaak-plugins/bindings/serde_json/*.ts ./src/bindings/serde_json",
|
||||
"publish": "npm publish",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpy-cli": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PluginVersion } from "./gen_search.js";
|
||||
|
||||
export type PluginNameVersion = { name: string, version: string, };
|
||||
|
||||
export type PluginSearchResponse = { plugins: Array<PluginVersion>, };
|
||||
|
||||
export type PluginUpdatesResponse = { plugins: Array<PluginNameVersion>, };
|
||||
@@ -1,481 +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, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace } from "./gen_models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
export type BootResponse = { name: string, version: string, };
|
||||
|
||||
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
|
||||
|
||||
export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, };
|
||||
|
||||
export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: string, args: CallHttpAuthenticationActionArgs, };
|
||||
|
||||
export type CallHttpAuthenticationRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, method: string, url: string, headers: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpAuthenticationResponse = {
|
||||
/**
|
||||
* HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
* new headers will be added.
|
||||
*/
|
||||
setHeaders: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null, };
|
||||
|
||||
export type CloseWindowRequest = { label: string, };
|
||||
|
||||
export type Color = "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
|
||||
|
||||
export type CompletionOptionType = "constant" | "variable";
|
||||
|
||||
export type Content = { "type": "text", content: string, } | { "type": "markdown", content: string, };
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type DeleteKeyValueRequest = { key: string, };
|
||||
|
||||
export type DeleteKeyValueResponse = { deleted: boolean, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ErrorResponse = { error: string, };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
export type ExportHttpRequestResponse = { content: string, };
|
||||
|
||||
export type FileFilter = { name: string,
|
||||
/**
|
||||
* File extensions to require
|
||||
*/
|
||||
extensions: Array<string>, };
|
||||
|
||||
export type FilterRequest = { content: string, filter: string, };
|
||||
|
||||
export type FilterResponse = { content: string, error?: string, };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
|
||||
|
||||
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
|
||||
|
||||
export type FormInputBanner = { inputs?: Array<FormInput>, hidden?: boolean, color?: Color, };
|
||||
|
||||
export type FormInputBase = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputCheckbox = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Don't show the editor gutter (line numbers, folds, etc.)
|
||||
*/
|
||||
hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, readOnly?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputFile = {
|
||||
/**
|
||||
* The title of the file selection window
|
||||
*/
|
||||
title: string,
|
||||
/**
|
||||
* Allow selecting multiple files
|
||||
*/
|
||||
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<FileFilter>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputHttpRequest = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputMarkdown = { content: string, hidden?: boolean, };
|
||||
|
||||
export type FormInputSelect = {
|
||||
/**
|
||||
* The options that will be available in the select input
|
||||
*/
|
||||
options: Array<FormInputSelectOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputSelectOption = { label: string, value: string, };
|
||||
|
||||
export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean,
|
||||
/**
|
||||
* Whether to allow newlines in the input, like a <textarea/>
|
||||
*/
|
||||
multiLine?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean,
|
||||
/**
|
||||
* Longer description of the input, likely shown in a tooltip
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
|
||||
|
||||
export type GetCookieValueRequest = { name: string, };
|
||||
|
||||
export type GetCookieValueResponse = { value: string | null, };
|
||||
|
||||
export type GetGrpcRequestActionsResponse = { actions: Array<GrpcRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
|
||||
|
||||
export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, };
|
||||
|
||||
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string, };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
|
||||
|
||||
export type GetKeyValueRequest = { key: string, };
|
||||
|
||||
export type GetKeyValueResponse = { value?: string, };
|
||||
|
||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||
|
||||
export type GetThemesRequest = Record<string, never>;
|
||||
|
||||
export type GetThemesResponse = { themes: Array<Theme>, };
|
||||
|
||||
export type GrpcRequestAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type HttpAuthenticationAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type HttpHeader = { name: string, value: string, };
|
||||
|
||||
export type HttpRequestAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type Icon = "alert_triangle" | "check" | "check_circle" | "chevron_down" | "copy" | "info" | "pin" | "search" | "trash" | "_unknown";
|
||||
|
||||
export type ImportRequest = { content: string, };
|
||||
|
||||
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
||||
|
||||
export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
export type ListCookieNamesRequest = {};
|
||||
|
||||
export type ListCookieNamesResponse = { names: Array<string>, };
|
||||
|
||||
export type OpenWindowRequest = { url: string,
|
||||
/**
|
||||
* Label for the window. If not provided, a random one will be generated.
|
||||
*/
|
||||
label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
|
||||
|
||||
export type PluginWindowContext = { "type": "none" } | { "type": "label", label: string, workspace_id: string | null, };
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string,
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
required?: boolean, };
|
||||
|
||||
export type PromptTextResponse = { value: string | null, };
|
||||
|
||||
export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: Partial<HttpRequest>, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
|
||||
|
||||
export type SetKeyValueRequest = { key: string, value: string, };
|
||||
|
||||
export type SetKeyValueResponse = {};
|
||||
|
||||
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>, };
|
||||
|
||||
/**
|
||||
* Similar to FormInput, but contains
|
||||
*/
|
||||
export type TemplateFunctionArg = FormInput;
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
|
||||
export type Theme = {
|
||||
/**
|
||||
* How the theme is identified. This should never be changed
|
||||
*/
|
||||
id: string,
|
||||
/**
|
||||
* The friendly name of the theme to be displayed to the user
|
||||
*/
|
||||
label: string,
|
||||
/**
|
||||
* Whether the theme will be used for dark or light appearance
|
||||
*/
|
||||
dark: boolean,
|
||||
/**
|
||||
* The default top-level colors for the theme
|
||||
*/
|
||||
base: ThemeComponentColors,
|
||||
/**
|
||||
* Optionally override theme for individual UI components for more control
|
||||
*/
|
||||
components?: ThemeComponents, };
|
||||
|
||||
export type ThemeComponentColors = { surface?: string, surfaceHighlight?: string, surfaceActive?: string, text?: string, textSubtle?: string, textSubtlest?: string, border?: string, borderSubtle?: string, borderFocus?: string, shadow?: string, backdrop?: string, selection?: string, primary?: string, secondary?: string, info?: string, success?: string, notice?: string, warning?: string, danger?: string, };
|
||||
|
||||
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
|
||||
|
||||
export type WindowNavigateEvent = { url: string, };
|
||||
|
||||
export type WindowSize = { width: number, height: number, };
|
||||
@@ -1,25 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, base: boolean, variables: Array<EnvironmentVariable>, color: string | null, };
|
||||
|
||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: 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, 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, id?: string, };
|
||||
|
||||
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
|
||||
@@ -1,5 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginMetadata = { version: string, name: string, displayName: string, description: string | null, homepageUrl: string | null, repositoryUrl: string | null, };
|
||||
|
||||
export type PluginVersion = { id: string, version: string, url: string, description: string | null, name: string, displayName: string, homepageUrl: string | null, repositoryUrl: string | null, checksum: string, readme: string | null, yanked: boolean, };
|
||||
@@ -1,3 +0,0 @@
|
||||
// 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 | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null;
|
||||
@@ -1,2 +0,0 @@
|
||||
export type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
export type MaybePromise<T> = Promise<T> | T;
|
||||
@@ -1,9 +0,0 @@
|
||||
export type * from './plugins';
|
||||
export type * from './themes';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
|
||||
// Some extras for utility
|
||||
|
||||
export type { PartialImportResources } from './plugins/ImporterPlugin';
|
||||
@@ -1,29 +0,0 @@
|
||||
import {
|
||||
CallHttpAuthenticationActionArgs,
|
||||
CallHttpAuthenticationRequest,
|
||||
CallHttpAuthenticationResponse,
|
||||
FormInput,
|
||||
GetHttpAuthenticationConfigRequest,
|
||||
GetHttpAuthenticationSummaryResponse,
|
||||
HttpAuthenticationAction,
|
||||
} from '../bindings/gen_events';
|
||||
import { MaybePromise } from '../helpers';
|
||||
import { Context } from './Context';
|
||||
|
||||
type DynamicFormInput = FormInput & {
|
||||
dynamic(
|
||||
ctx: Context,
|
||||
args: GetHttpAuthenticationConfigRequest,
|
||||
): MaybePromise<Partial<FormInput> | undefined | null>;
|
||||
};
|
||||
|
||||
export type AuthenticationPlugin = GetHttpAuthenticationSummaryResponse & {
|
||||
args: (FormInput | DynamicFormInput)[];
|
||||
onApply(
|
||||
ctx: Context,
|
||||
args: CallHttpAuthenticationRequest,
|
||||
): MaybePromise<CallHttpAuthenticationResponse>;
|
||||
actions?: (HttpAuthenticationAction & {
|
||||
onSelect(ctx: Context, args: CallHttpAuthenticationActionArgs): Promise<void> | void;
|
||||
})[];
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
import type {
|
||||
FindHttpResponsesRequest,
|
||||
FindHttpResponsesResponse,
|
||||
GetCookieValueRequest,
|
||||
GetCookieValueResponse,
|
||||
GetHttpRequestByIdRequest,
|
||||
GetHttpRequestByIdResponse,
|
||||
ListCookieNamesResponse,
|
||||
OpenWindowRequest,
|
||||
PromptTextRequest,
|
||||
PromptTextResponse,
|
||||
RenderGrpcRequestRequest,
|
||||
RenderGrpcRequestResponse,
|
||||
RenderHttpRequestRequest,
|
||||
RenderHttpRequestResponse,
|
||||
SendHttpRequestRequest,
|
||||
SendHttpRequestResponse,
|
||||
ShowToastRequest,
|
||||
TemplateRenderRequest,
|
||||
TemplateRenderResponse,
|
||||
} from '../bindings/gen_events.ts';
|
||||
|
||||
export interface Context {
|
||||
clipboard: {
|
||||
copyText(text: string): Promise<void>;
|
||||
};
|
||||
toast: {
|
||||
show(args: ShowToastRequest): Promise<void>;
|
||||
};
|
||||
prompt: {
|
||||
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
|
||||
};
|
||||
store: {
|
||||
set<T>(key: string, value: T): Promise<void>;
|
||||
get<T>(key: string): Promise<T | undefined>;
|
||||
delete(key: string): Promise<boolean>;
|
||||
};
|
||||
window: {
|
||||
openUrl(
|
||||
args: OpenWindowRequest & {
|
||||
onNavigate?: (args: { url: string }) => void;
|
||||
onClose?: () => void;
|
||||
},
|
||||
): Promise<{ close: () => void }>;
|
||||
};
|
||||
cookies: {
|
||||
listNames(): Promise<ListCookieNamesResponse['names']>;
|
||||
getValue(args: GetCookieValueRequest): Promise<GetCookieValueResponse['value']>;
|
||||
};
|
||||
grpcRequest: {
|
||||
render(args: RenderGrpcRequestRequest): Promise<RenderGrpcRequestResponse['grpcRequest']>;
|
||||
};
|
||||
httpRequest: {
|
||||
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
|
||||
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
|
||||
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>;
|
||||
};
|
||||
httpResponse: {
|
||||
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
|
||||
};
|
||||
templates: {
|
||||
render(args: TemplateRenderRequest): Promise<TemplateRenderResponse['data']>;
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { FilterResponse } from '../bindings/gen_events';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type FilterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
onFilter(
|
||||
ctx: Context,
|
||||
args: { payload: string; filter: string; mimeType: string },
|
||||
): Promise<FilterResponse> | FilterResponse;
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
import { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type GrpcRequestActionPlugin = GrpcRequestAction & {
|
||||
onSelect(ctx: Context, args: CallGrpcRequestActionArgs): Promise<void> | void;
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { CallHttpRequestActionArgs, HttpRequestAction } from '../bindings/gen_events';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type HttpRequestActionPlugin = HttpRequestAction & {
|
||||
onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void;
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ImportResources } from '../bindings/gen_events';
|
||||
import { AtLeast, MaybePromise } from '../helpers';
|
||||
import type { Context } from './Context';
|
||||
|
||||
type RootFields = 'name' | 'id' | 'model';
|
||||
type CommonFields = RootFields | 'workspaceId';
|
||||
|
||||
export type PartialImportResources = {
|
||||
workspaces: Array<AtLeast<ImportResources['workspaces'][0], RootFields>>;
|
||||
environments: Array<AtLeast<ImportResources['environments'][0], CommonFields>>;
|
||||
folders: Array<AtLeast<ImportResources['folders'][0], CommonFields>>;
|
||||
httpRequests: Array<AtLeast<ImportResources['httpRequests'][0], CommonFields>>;
|
||||
grpcRequests: Array<AtLeast<ImportResources['grpcRequests'][0], CommonFields>>;
|
||||
websocketRequests: Array<AtLeast<ImportResources['websocketRequests'][0], CommonFields>>;
|
||||
};
|
||||
|
||||
export type ImportPluginResponse = null | {
|
||||
resources: PartialImportResources;
|
||||
};
|
||||
|
||||
export type ImporterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
onImport(
|
||||
ctx: Context,
|
||||
args: { text: string },
|
||||
): MaybePromise<ImportPluginResponse | null | undefined>;
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import {
|
||||
CallTemplateFunctionArgs,
|
||||
TemplateFunction,
|
||||
} from "../bindings/gen_events";
|
||||
import { Context } from "./Context";
|
||||
|
||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
||||
onRender(
|
||||
ctx: Context,
|
||||
args: CallTemplateFunctionArgs,
|
||||
): Promise<string | null>;
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { Theme } from '../bindings/gen_events';
|
||||
|
||||
export type ThemePlugin = Theme;
|
||||
@@ -1,22 +0,0 @@
|
||||
import { AuthenticationPlugin } from './AuthenticationPlugin';
|
||||
import type { FilterPlugin } from './FilterPlugin';
|
||||
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
||||
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||
import type { ImporterPlugin } from './ImporterPlugin';
|
||||
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
||||
import type { ThemePlugin } from './ThemePlugin';
|
||||
|
||||
export type { Context } from './Context';
|
||||
|
||||
/**
|
||||
* The global structure of a Yaak plugin
|
||||
*/
|
||||
export type PluginDefinition = {
|
||||
importer?: ImporterPlugin;
|
||||
themes?: ThemePlugin[];
|
||||
filter?: FilterPlugin;
|
||||
authentication?: AuthenticationPlugin;
|
||||
httpRequestActions?: HttpRequestActionPlugin[];
|
||||
grpcRequestActions?: GrpcRequestActionPlugin[];
|
||||
templateFunctions?: TemplateFunctionPlugin[];
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
export type Colors = {
|
||||
surface: string;
|
||||
surfaceHighlight?: string;
|
||||
surfaceActive?: string;
|
||||
|
||||
text: string;
|
||||
textSubtle?: string;
|
||||
textSubtlest?: string;
|
||||
|
||||
border?: string;
|
||||
borderSubtle?: string;
|
||||
borderFocus?: string;
|
||||
|
||||
shadow?: string;
|
||||
backdrop?: string;
|
||||
selection?: string;
|
||||
|
||||
primary?: string;
|
||||
secondary?: string;
|
||||
info?: string;
|
||||
success?: string;
|
||||
notice?: string;
|
||||
warning?: string;
|
||||
danger?: string;
|
||||
};
|
||||
|
||||
export type Index = Colors & {
|
||||
id: string;
|
||||
name: string;
|
||||
components?: Partial<{
|
||||
dialog: Partial<Colors>;
|
||||
menu: Partial<Colors>;
|
||||
toast: Partial<Colors>;
|
||||
sidebar: Partial<Colors>;
|
||||
responsePane: Partial<Colors>;
|
||||
appHeader: Partial<Colors>;
|
||||
button: Partial<Colors>;
|
||||
banner: Partial<Colors>;
|
||||
placeholder: Partial<Colors>;
|
||||
urlBar: Partial<Colors>;
|
||||
editor: Partial<Colors>;
|
||||
input: Partial<Colors>;
|
||||
}>;
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"es2021",
|
||||
"dom"
|
||||
],
|
||||
"declaration": true,
|
||||
"declarationDir": "./lib",
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/index.ts"
|
||||
]
|
||||
}
|
||||
10
packages/plugin-runtime/package-lock.json
generated
10
packages/plugin-runtime/package-lock.json
generated
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/plugin-runtime",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@yaakapp-internal/plugin-runtime"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.13"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { InternalEvent } from '@yaakapp/api';
|
||||
|
||||
export class EventChannel {
|
||||
#listeners = new Set<(event: InternalEvent) => void>();
|
||||
|
||||
emit(e: InternalEvent) {
|
||||
for (const l of this.#listeners) {
|
||||
l(e);
|
||||
}
|
||||
}
|
||||
|
||||
listen(cb: (e: InternalEvent) => void) {
|
||||
this.#listeners.add(cb);
|
||||
}
|
||||
|
||||
unlisten(cb: (e: InternalEvent) => void) {
|
||||
this.#listeners.delete(cb);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { BootRequest, InternalEvent } from '@yaakapp/api';
|
||||
import type { EventChannel } from './EventChannel';
|
||||
import { PluginInstance, PluginWorkerData } from './PluginInstance';
|
||||
|
||||
export class PluginHandle {
|
||||
#instance: PluginInstance;
|
||||
|
||||
constructor(
|
||||
readonly pluginRefId: string,
|
||||
readonly bootRequest: BootRequest,
|
||||
readonly pluginToAppEvents: EventChannel,
|
||||
) {
|
||||
const workerData: PluginWorkerData = {
|
||||
pluginRefId: this.pluginRefId,
|
||||
bootRequest: this.bootRequest,
|
||||
};
|
||||
this.#instance = new PluginInstance(workerData, pluginToAppEvents);
|
||||
}
|
||||
|
||||
sendToWorker(event: InternalEvent) {
|
||||
this.#instance.postMessage(event);
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.#instance.terminate();
|
||||
}
|
||||
}
|
||||
@@ -1,655 +0,0 @@
|
||||
import {
|
||||
BootRequest,
|
||||
BootResponse,
|
||||
DeleteKeyValueResponse,
|
||||
FindHttpResponsesResponse,
|
||||
FormInput,
|
||||
GetCookieValueRequest,
|
||||
GetCookieValueResponse,
|
||||
GetHttpRequestByIdResponse,
|
||||
GetKeyValueResponse,
|
||||
GrpcRequestAction,
|
||||
HttpAuthenticationAction,
|
||||
HttpRequestAction,
|
||||
InternalEvent,
|
||||
InternalEventPayload,
|
||||
ListCookieNamesResponse,
|
||||
PluginWindowContext,
|
||||
PromptTextResponse,
|
||||
RenderGrpcRequestResponse,
|
||||
RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
TemplateFunction,
|
||||
TemplateFunctionArg,
|
||||
TemplateRenderResponse,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import { JsonValue } from '@yaakapp/api/lib/bindings/serde_json/JsonValue';
|
||||
import console from 'node:console';
|
||||
import { readFileSync, type Stats, statSync, watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { EventChannel } from './EventChannel';
|
||||
import { migrateTemplateFunctionSelectOptions } from './migrations';
|
||||
|
||||
export interface PluginWorkerData {
|
||||
bootRequest: BootRequest;
|
||||
pluginRefId: string;
|
||||
}
|
||||
|
||||
export class PluginInstance {
|
||||
#workerData: PluginWorkerData;
|
||||
#mod: PluginDefinition;
|
||||
#pkg: { name?: string; version?: string };
|
||||
#pluginToAppEvents: EventChannel;
|
||||
#appToPluginEvents: EventChannel;
|
||||
|
||||
constructor(workerData: PluginWorkerData, pluginEvents: EventChannel) {
|
||||
this.#workerData = workerData;
|
||||
this.#pluginToAppEvents = pluginEvents;
|
||||
this.#appToPluginEvents = new EventChannel();
|
||||
|
||||
// Forward incoming events to onMessage()
|
||||
this.#appToPluginEvents.listen(async (event) => {
|
||||
await this.#onMessage(event);
|
||||
});
|
||||
|
||||
// Reload plugin if the JS or package.json changes
|
||||
const windowContextNone: PluginWindowContext = { type: 'none' };
|
||||
|
||||
this.#mod = {};
|
||||
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8'));
|
||||
|
||||
const bootResponse: BootResponse = {
|
||||
name: this.#pkg.name ?? 'unknown',
|
||||
version: this.#pkg.version ?? '0.0.1',
|
||||
};
|
||||
|
||||
const fileChangeCallback = async () => {
|
||||
this.#importModule();
|
||||
return this.#sendPayload(
|
||||
windowContextNone,
|
||||
{ type: 'reload_response', ...bootResponse },
|
||||
null,
|
||||
);
|
||||
};
|
||||
|
||||
if (this.#workerData.bootRequest.watch) {
|
||||
watchFile(this.#pathMod(), fileChangeCallback);
|
||||
watchFile(this.#pathPkg(), fileChangeCallback);
|
||||
}
|
||||
|
||||
this.#importModule();
|
||||
}
|
||||
|
||||
postMessage(event: InternalEvent) {
|
||||
this.#appToPluginEvents.emit(event);
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.#unimportModule();
|
||||
}
|
||||
|
||||
async #onMessage(event: InternalEvent) {
|
||||
const ctx = this.#newCtx(event);
|
||||
|
||||
const { windowContext, payload, id: replyId } = event;
|
||||
try {
|
||||
if (payload.type === 'boot_request') {
|
||||
// console.log('Plugin initialized', pkg.name, { capabilities, enableWatch });
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'boot_response',
|
||||
name: this.#pkg.name ?? 'unknown',
|
||||
version: this.#pkg.version ?? '0.0.1',
|
||||
};
|
||||
this.#sendPayload(windowContext, payload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'terminate_request') {
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'terminate_response',
|
||||
};
|
||||
this.#sendPayload(windowContext, payload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'import_request' &&
|
||||
typeof this.#mod?.importer?.onImport === 'function'
|
||||
) {
|
||||
const reply = await this.#mod.importer.onImport(ctx, {
|
||||
text: payload.content,
|
||||
});
|
||||
if (reply != null) {
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'import_response',
|
||||
// deno-lint-ignore no-explicit-any
|
||||
resources: reply.resources as any,
|
||||
};
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
} else {
|
||||
// Continue, to send back an empty reply
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.type === 'filter_request' && typeof this.#mod?.filter?.onFilter === 'function') {
|
||||
const reply = await this.#mod.filter.onFilter(ctx, {
|
||||
filter: payload.filter,
|
||||
payload: payload.content,
|
||||
mimeType: payload.type,
|
||||
});
|
||||
this.#sendPayload(windowContext, { type: 'filter_response', ...reply }, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_grpc_request_actions_request' &&
|
||||
Array.isArray(this.#mod?.grpcRequestActions)
|
||||
) {
|
||||
const reply: GrpcRequestAction[] = this.#mod.grpcRequestActions.map((a) => ({
|
||||
...a,
|
||||
// Add everything except onSelect
|
||||
onSelect: undefined,
|
||||
}));
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_grpc_request_actions_response',
|
||||
pluginRefId: this.#workerData.pluginRefId,
|
||||
actions: reply,
|
||||
};
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_http_request_actions_request' &&
|
||||
Array.isArray(this.#mod?.httpRequestActions)
|
||||
) {
|
||||
const reply: HttpRequestAction[] = this.#mod.httpRequestActions.map((a) => ({
|
||||
...a,
|
||||
// Add everything except onSelect
|
||||
onSelect: undefined,
|
||||
}));
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_http_request_actions_response',
|
||||
pluginRefId: this.#workerData.pluginRefId,
|
||||
actions: reply,
|
||||
};
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'get_themes_request' && Array.isArray(this.#mod?.themes)) {
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_themes_response',
|
||||
themes: this.#mod.themes,
|
||||
};
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_template_functions_request' &&
|
||||
Array.isArray(this.#mod?.templateFunctions)
|
||||
) {
|
||||
const reply: TemplateFunction[] = this.#mod.templateFunctions.map((templateFunction) => {
|
||||
return {
|
||||
...migrateTemplateFunctionSelectOptions(templateFunction),
|
||||
// Add everything except render
|
||||
onRender: undefined,
|
||||
};
|
||||
});
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_template_functions_response',
|
||||
pluginRefId: this.#workerData.pluginRefId,
|
||||
functions: reply,
|
||||
};
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'get_http_authentication_summary_request' && this.#mod?.authentication) {
|
||||
const { name, shortLabel, label } = this.#mod.authentication;
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_http_authentication_summary_response',
|
||||
name,
|
||||
label,
|
||||
shortLabel,
|
||||
};
|
||||
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'get_http_authentication_config_request' && this.#mod?.authentication) {
|
||||
const { args, actions } = this.#mod.authentication;
|
||||
const resolvedArgs: FormInput[] = [];
|
||||
for (const v of args) {
|
||||
if (v && 'dynamic' in v) {
|
||||
const dynamicAttrs = await v.dynamic(ctx, payload);
|
||||
const { dynamic, ...other } = v;
|
||||
resolvedArgs.push({ ...other, ...dynamicAttrs } as FormInput);
|
||||
} else if (v) {
|
||||
resolvedArgs.push(v);
|
||||
}
|
||||
}
|
||||
const resolvedActions: HttpAuthenticationAction[] = [];
|
||||
for (const { onSelect, ...action } of actions ?? []) {
|
||||
resolvedActions.push(action);
|
||||
}
|
||||
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_http_authentication_config_response',
|
||||
args: resolvedArgs,
|
||||
actions: resolvedActions,
|
||||
pluginRefId: this.#workerData.pluginRefId,
|
||||
};
|
||||
|
||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'call_http_authentication_request' && this.#mod?.authentication) {
|
||||
const auth = this.#mod.authentication;
|
||||
if (typeof auth?.onApply === 'function') {
|
||||
applyFormInputDefaults(auth.args, payload.values);
|
||||
const result = await auth.onApply(ctx, payload);
|
||||
this.#sendPayload(
|
||||
windowContext,
|
||||
{
|
||||
type: 'call_http_authentication_response',
|
||||
setHeaders: result.setHeaders,
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_authentication_action_request' &&
|
||||
this.#mod.authentication != null
|
||||
) {
|
||||
const action = this.#mod.authentication.actions?.[payload.index];
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
this.#sendEmpty(windowContext, replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_request_action_request' &&
|
||||
Array.isArray(this.#mod.httpRequestActions)
|
||||
) {
|
||||
const action = this.#mod.httpRequestActions[payload.index];
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
this.#sendEmpty(windowContext, replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_grpc_request_action_request' &&
|
||||
Array.isArray(this.#mod.grpcRequestActions)
|
||||
) {
|
||||
const action = this.#mod.grpcRequestActions[payload.index];
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
this.#sendEmpty(windowContext, replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_template_function_request' &&
|
||||
Array.isArray(this.#mod?.templateFunctions)
|
||||
) {
|
||||
const fn = this.#mod.templateFunctions.find((a) => a.name === payload.name);
|
||||
if (typeof fn?.onRender === 'function') {
|
||||
applyFormInputDefaults(fn.args, payload.args.values);
|
||||
const result = await fn.onRender(ctx, payload.args);
|
||||
this.#sendPayload(
|
||||
windowContext,
|
||||
{
|
||||
type: 'call_template_function_response',
|
||||
value: result ?? null,
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.type === 'reload_request') {
|
||||
this.#importModule();
|
||||
}
|
||||
} catch (err) {
|
||||
const error = `${err}`.replace(/^Error:\s*/g, '');
|
||||
console.log('Plugin call threw exception', payload.type, '→', error);
|
||||
this.#sendPayload(windowContext, { type: 'error_response', error }, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
// No matches, so send back an empty response so the caller doesn't block forever
|
||||
this.#sendEmpty(windowContext, replyId);
|
||||
}
|
||||
|
||||
#pathMod() {
|
||||
return path.posix.join(this.#workerData.bootRequest.dir, 'build', 'index.js');
|
||||
}
|
||||
|
||||
#pathPkg() {
|
||||
return path.join(this.#workerData.bootRequest.dir, 'package.json');
|
||||
}
|
||||
|
||||
#unimportModule() {
|
||||
const id = require.resolve(this.#pathMod());
|
||||
delete require.cache[id];
|
||||
}
|
||||
|
||||
#importModule() {
|
||||
const id = require.resolve(this.#pathMod());
|
||||
delete require.cache[id];
|
||||
this.#mod = require(id).plugin;
|
||||
}
|
||||
|
||||
#buildEventToSend(
|
||||
windowContext: PluginWindowContext,
|
||||
payload: InternalEventPayload,
|
||||
replyId: string | null = null,
|
||||
): InternalEvent {
|
||||
return {
|
||||
pluginRefId: this.#workerData.pluginRefId,
|
||||
pluginName: path.basename(this.#workerData.bootRequest.dir),
|
||||
id: genId(),
|
||||
replyId,
|
||||
payload,
|
||||
windowContext,
|
||||
};
|
||||
}
|
||||
|
||||
#sendPayload(
|
||||
windowContext: PluginWindowContext,
|
||||
payload: InternalEventPayload,
|
||||
replyId: string | null,
|
||||
): string {
|
||||
const event = this.#buildEventToSend(windowContext, payload, replyId);
|
||||
this.#sendEvent(event);
|
||||
return event.id;
|
||||
}
|
||||
|
||||
#sendEvent(event: InternalEvent) {
|
||||
// if (event.payload.type !== 'empty_response') {
|
||||
// console.log('Sending event to app', this.#pkg.name, event.id, event.payload.type);
|
||||
// }
|
||||
this.#pluginToAppEvents.emit(event);
|
||||
}
|
||||
|
||||
#sendEmpty(windowContext: PluginWindowContext, replyId: string | null = null): string {
|
||||
return this.#sendPayload(windowContext, { type: 'empty_response' }, replyId);
|
||||
}
|
||||
|
||||
#sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||
windowContext: PluginWindowContext,
|
||||
payload: InternalEventPayload,
|
||||
): Promise<T> {
|
||||
// 1. Build event to send
|
||||
const eventToSend = this.#buildEventToSend(windowContext, payload, null);
|
||||
|
||||
// 2. Spawn listener in background
|
||||
const promise = new Promise<T>((resolve) => {
|
||||
const cb = (event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
this.#appToPluginEvents.unlisten(cb); // Unlisten, now that we're done
|
||||
const { type: _, ...payload } = event.payload;
|
||||
resolve(payload as T);
|
||||
}
|
||||
};
|
||||
this.#appToPluginEvents.listen(cb);
|
||||
});
|
||||
|
||||
// 3. Send the event after we start listening (to prevent race)
|
||||
this.#sendEvent(eventToSend);
|
||||
|
||||
// 4. Return the listener promise
|
||||
return promise as unknown as Promise<T>;
|
||||
}
|
||||
|
||||
#sendAndListenForEvents(
|
||||
windowContext: PluginWindowContext,
|
||||
payload: InternalEventPayload,
|
||||
onEvent: (event: InternalEventPayload) => void,
|
||||
): void {
|
||||
// 1. Build event to send
|
||||
const eventToSend = this.#buildEventToSend(windowContext, payload, null);
|
||||
|
||||
// 2. Listen for replies in the background
|
||||
this.#appToPluginEvents.listen((event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
onEvent(event.payload);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Send the event after we start listening (to prevent race)
|
||||
this.#sendEvent(eventToSend);
|
||||
}
|
||||
|
||||
#newCtx(event: InternalEvent): Context {
|
||||
return {
|
||||
clipboard: {
|
||||
copyText: async (text) => {
|
||||
await this.#sendAndWaitForReply(event.windowContext, {
|
||||
type: 'copy_text_request',
|
||||
text,
|
||||
});
|
||||
},
|
||||
},
|
||||
toast: {
|
||||
show: async (args) => {
|
||||
await this.#sendAndWaitForReply(event.windowContext, {
|
||||
type: 'show_toast_request',
|
||||
...args,
|
||||
});
|
||||
},
|
||||
},
|
||||
window: {
|
||||
openUrl: async ({ onNavigate, onClose, ...args }) => {
|
||||
args.label = args.label || `${Math.random()}`;
|
||||
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
|
||||
const onEvent = (event: InternalEventPayload) => {
|
||||
if (event.type === 'window_navigate_event') {
|
||||
onNavigate?.(event);
|
||||
} else if (event.type === 'window_close_event') {
|
||||
onClose?.();
|
||||
}
|
||||
};
|
||||
this.#sendAndListenForEvents(event.windowContext, payload, onEvent);
|
||||
return {
|
||||
close: () => {
|
||||
const closePayload: InternalEventPayload = {
|
||||
type: 'close_window_request',
|
||||
label: args.label,
|
||||
};
|
||||
this.#sendPayload(event.windowContext, closePayload, null);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
prompt: {
|
||||
text: async (args) => {
|
||||
const reply: PromptTextResponse = await this.#sendAndWaitForReply(event.windowContext, {
|
||||
type: 'prompt_text_request',
|
||||
...args,
|
||||
});
|
||||
return reply.value;
|
||||
},
|
||||
},
|
||||
httpResponse: {
|
||||
find: async (args) => {
|
||||
const payload = {
|
||||
type: 'find_http_responses_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpResponses;
|
||||
},
|
||||
},
|
||||
grpcRequest: {
|
||||
render: async (args) => {
|
||||
const payload = {
|
||||
type: 'render_grpc_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return grpcRequest;
|
||||
},
|
||||
},
|
||||
httpRequest: {
|
||||
getById: async (args) => {
|
||||
const payload = {
|
||||
type: 'get_http_request_by_id_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpRequest;
|
||||
},
|
||||
send: async (args) => {
|
||||
const payload = {
|
||||
type: 'send_http_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpResponse;
|
||||
},
|
||||
render: async (args) => {
|
||||
const payload = {
|
||||
type: 'render_http_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return httpRequest;
|
||||
},
|
||||
},
|
||||
cookies: {
|
||||
getValue: async (args: GetCookieValueRequest) => {
|
||||
const payload = {
|
||||
type: 'get_cookie_value_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return value;
|
||||
},
|
||||
listNames: async () => {
|
||||
const payload = { type: 'list_cookie_names_request' } as const;
|
||||
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return names;
|
||||
},
|
||||
},
|
||||
templates: {
|
||||
/**
|
||||
* Invoke Yaak's template engine to render a value. If the value is a nested type
|
||||
* (eg. object), it will be recursively rendered.
|
||||
*/
|
||||
render: async (args) => {
|
||||
const payload = { type: 'template_render_request', ...args } as const;
|
||||
const result = await this.#sendAndWaitForReply<TemplateRenderResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return result.data;
|
||||
},
|
||||
},
|
||||
store: {
|
||||
get: async <T>(key: string) => {
|
||||
const payload = { type: 'get_key_value_request', key } as const;
|
||||
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return result.value ? (JSON.parse(result.value) as T) : undefined;
|
||||
},
|
||||
set: async <T>(key: string, value: T) => {
|
||||
const valueStr = JSON.stringify(value);
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'set_key_value_request',
|
||||
key,
|
||||
value: valueStr,
|
||||
};
|
||||
await this.#sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
|
||||
},
|
||||
delete: async (key: string) => {
|
||||
const payload = { type: 'delete_key_value_request', key } as const;
|
||||
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return result.deleted;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function genId(len = 5): string {
|
||||
const alphabet = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let id = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
id += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Recursively apply form input defaults to a set of values */
|
||||
function applyFormInputDefaults(
|
||||
inputs: TemplateFunctionArg[],
|
||||
values: { [p: string]: JsonValue | undefined },
|
||||
) {
|
||||
for (const input of inputs) {
|
||||
if ('inputs' in input) {
|
||||
applyFormInputDefaults(input.inputs ?? [], values);
|
||||
} else if ('defaultValue' in input && values[input.name] === undefined) {
|
||||
values[input.name] = input.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const watchedFiles: Record<string, Stats | null> = {};
|
||||
|
||||
/**
|
||||
* Watch a file and trigger a callback on change.
|
||||
*
|
||||
* We also track the stat for each file because fs.watch() will
|
||||
* trigger a "change" event when the access date changes.
|
||||
*/
|
||||
function watchFile(filepath: string, cb: () => void) {
|
||||
watch(filepath, () => {
|
||||
const stat = statSync(filepath, { throwIfNoEntry: false });
|
||||
if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
|
||||
watchedFiles[filepath] = stat ?? null;
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { InternalEvent } from '@yaakapp/api';
|
||||
import { EventChannel } from './EventChannel';
|
||||
import { PluginHandle } from './PluginHandle';
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const port = process.env.PORT;
|
||||
if (!port) {
|
||||
throw new Error('Plugin runtime missing PORT')
|
||||
}
|
||||
|
||||
const pluginToAppEvents = new EventChannel();
|
||||
const plugins: Record<string, PluginHandle> = {};
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${port}`);
|
||||
|
||||
ws.on('message', async (e: Buffer) => {
|
||||
try {
|
||||
await handleIncoming(e.toString());
|
||||
} catch (err) {
|
||||
console.log('Failed to handle incoming plugin event', err);
|
||||
}
|
||||
});
|
||||
ws.on('open', () => console.log('Plugin runtime connected to websocket'));
|
||||
ws.on('error', (err: any) => console.error('Plugin runtime websocket error', err));
|
||||
ws.on('close', (code: number) => console.log('Plugin runtime websocket closed', code));
|
||||
|
||||
// Listen for incoming events from plugins
|
||||
pluginToAppEvents.listen((e) => {
|
||||
const eventStr = JSON.stringify(e);
|
||||
ws.send(eventStr);
|
||||
});
|
||||
|
||||
async function handleIncoming(msg: string) {
|
||||
const pluginEvent: InternalEvent = JSON.parse(msg);
|
||||
// Handle special event to bootstrap plugin
|
||||
if (pluginEvent.payload.type === 'boot_request') {
|
||||
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, pluginToAppEvents);
|
||||
plugins[pluginEvent.pluginRefId] = plugin;
|
||||
}
|
||||
|
||||
// Once booted, forward all events to the plugin worker
|
||||
const plugin = plugins[pluginEvent.pluginRefId];
|
||||
if (!plugin) {
|
||||
console.warn('Failed to get plugin for event by', pluginEvent.pluginRefId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginEvent.payload.type === 'terminate_request') {
|
||||
plugin.terminate();
|
||||
console.log('Terminated plugin worker', pluginEvent.pluginRefId);
|
||||
delete plugins[pluginEvent.pluginRefId];
|
||||
}
|
||||
|
||||
plugin.sendToWorker(pluginEvent);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import process from "node:process";
|
||||
|
||||
export function interceptStdout(
|
||||
intercept: (text: string) => string,
|
||||
) {
|
||||
const old_stdout_write = process.stdout.write;
|
||||
const old_stderr_write = process.stderr.write;
|
||||
|
||||
process.stdout.write = (function (write) {
|
||||
return function (text: string) {
|
||||
arguments[0] = interceptor(text, intercept);
|
||||
// deno-lint-ignore no-explicit-any
|
||||
write.apply(process.stdout, arguments as any);
|
||||
return true;
|
||||
};
|
||||
})(process.stdout.write);
|
||||
|
||||
process.stderr.write = (function (write) {
|
||||
return function (text: string) {
|
||||
arguments[0] = interceptor(text, intercept);
|
||||
// deno-lint-ignore no-explicit-any
|
||||
write.apply(process.stderr, arguments as any);
|
||||
return true;
|
||||
};
|
||||
})(process.stderr.write);
|
||||
|
||||
// puts back to original
|
||||
return function unhook() {
|
||||
process.stdout.write = old_stdout_write;
|
||||
process.stderr.write = old_stderr_write;
|
||||
};
|
||||
}
|
||||
|
||||
function interceptor(text: string, fn: (text: string) => string) {
|
||||
return fn(text).replace(/\n$/, "") +
|
||||
(fn(text) && /\n$/.test(text) ? "\n" : "");
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { TemplateFunction } from '@yaakapp/api';
|
||||
|
||||
export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): TemplateFunction {
|
||||
const migratedArgs = f.args.map((a) => {
|
||||
if (a.type === 'select') {
|
||||
a.options = a.options.map((o) => ({
|
||||
...o,
|
||||
label: o.label || (o as any).name,
|
||||
}));
|
||||
}
|
||||
return a;
|
||||
});
|
||||
|
||||
return {
|
||||
...f,
|
||||
args: migratedArgs,
|
||||
};
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2021",
|
||||
"lib": ["es2021"],
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node16",
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "build",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
"src/types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
1
plugins/.gitignore
vendored
1
plugins/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*/build
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/action-copy-curl",
|
||||
"displayName": "Copy as Curl",
|
||||
"description": "Copy request as a curl command",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/action-copy-grpcurl",
|
||||
"displayName": "Copy as gRPCurl",
|
||||
"description": "Copy gRPC request as a grpcurl command",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { GrpcRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import path from 'node:path';
|
||||
|
||||
const NEWLINE = '\\\n ';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
grpcRequestActions: [
|
||||
{
|
||||
label: 'Copy as gRPCurl',
|
||||
icon: 'copy',
|
||||
async onSelect(ctx, args) {
|
||||
const rendered_request = await ctx.grpcRequest.render({
|
||||
grpcRequest: args.grpcRequest,
|
||||
purpose: 'preview',
|
||||
});
|
||||
const data = await convert(rendered_request, args.protoFiles);
|
||||
await ctx.clipboard.copyText(data);
|
||||
await ctx.toast.show({
|
||||
message: 'Command copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export async function convert(request: Partial<GrpcRequest>, allProtoFiles: string[]) {
|
||||
const xs = ['grpcurl'];
|
||||
|
||||
if (request.url?.startsWith('http://')) {
|
||||
xs.push('-plaintext');
|
||||
}
|
||||
|
||||
const protoIncludes = allProtoFiles.filter((f) => !f.endsWith('.proto'));
|
||||
const protoFiles = allProtoFiles.filter((f) => f.endsWith('.proto'));
|
||||
|
||||
const inferredIncludes = new Set<string>();
|
||||
for (const f of protoFiles) {
|
||||
const protoDir = findParentProtoDir(f);
|
||||
if (protoDir) {
|
||||
inferredIncludes.add(protoDir);
|
||||
} else {
|
||||
inferredIncludes.add(path.join(f, '..'));
|
||||
inferredIncludes.add(path.join(f, '..', '..'));
|
||||
}
|
||||
}
|
||||
|
||||
for (const f of protoIncludes) {
|
||||
xs.push('-import-path', quote(f));
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
for (const f of inferredIncludes.values()) {
|
||||
xs.push('-import-path', quote(f));
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
for (const f of protoFiles) {
|
||||
xs.push('-proto', quote(f));
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
// Add headers
|
||||
for (const h of (request.metadata ?? []).filter(onlyEnabled)) {
|
||||
xs.push('-H', quote(`${h.name}: ${h.value}`));
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
// Add basic authentication
|
||||
if (request.authenticationType === 'basic') {
|
||||
const user = request.authentication?.username ?? '';
|
||||
const pass = request.authentication?.password ?? '';
|
||||
const encoded = btoa(`${user}:${pass}`);
|
||||
xs.push('-H', quote(`Authorization: Basic ${encoded}`));
|
||||
xs.push(NEWLINE);
|
||||
} else if (request.authenticationType === 'bearer') {
|
||||
// Add bearer authentication
|
||||
xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
// Add form params
|
||||
if (request.message) {
|
||||
xs.push('-d', `${quote(JSON.stringify(JSON.parse(request.message)))}`);
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
// Add the server address
|
||||
if (request.url) {
|
||||
const server = request.url.replace(/^https?:\/\//, ''); // remove protocol
|
||||
xs.push(server);
|
||||
}
|
||||
|
||||
// Add service + method
|
||||
if (request.service && request.method) {
|
||||
xs.push(`${request.service}/${request.method}`);
|
||||
}
|
||||
|
||||
xs.push(NEWLINE);
|
||||
|
||||
// Remove trailing newline
|
||||
if (xs[xs.length - 1] === NEWLINE) {
|
||||
xs.splice(xs.length - 1, 1);
|
||||
}
|
||||
|
||||
return xs.join(' ');
|
||||
}
|
||||
|
||||
function quote(arg: string): string {
|
||||
const escaped = arg.replace(/'/g, "\\'");
|
||||
return `'${escaped}'`;
|
||||
}
|
||||
|
||||
function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {
|
||||
return v.enabled !== false && !!v.name;
|
||||
}
|
||||
|
||||
function findParentProtoDir(startPath: string): string | null {
|
||||
let dir = path.resolve(startPath);
|
||||
|
||||
while (true) {
|
||||
if (path.basename(dir) === 'proto') {
|
||||
return dir;
|
||||
}
|
||||
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) {
|
||||
return null; // Reached root
|
||||
}
|
||||
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { convert } from '../src';
|
||||
|
||||
describe('exporter-curl', () => {
|
||||
test('Simple example', async () => {
|
||||
expect(
|
||||
await convert(
|
||||
{
|
||||
url: 'https://yaak.app',
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl yaak.app`].join(` \\\n `));
|
||||
});
|
||||
test('Basic metadata', async () => {
|
||||
expect(
|
||||
await convert(
|
||||
{
|
||||
url: 'https://yaak.app',
|
||||
metadata: [
|
||||
{ name: 'aaa', value: 'AAA' },
|
||||
{ enabled: true, name: 'bbb', value: 'BBB' },
|
||||
{ enabled: false, name: 'disabled', value: 'ddd' },
|
||||
],
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, `yaak.app`].join(` \\\n `));
|
||||
});
|
||||
test('Single proto file', async () => {
|
||||
expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual(
|
||||
[
|
||||
`grpcurl -import-path '/foo/bar'`,
|
||||
`-import-path '/foo'`,
|
||||
`-proto '/foo/bar/baz.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Multiple proto files, same dir', async () => {
|
||||
expect(
|
||||
await convert({ url: 'https://yaak.app' }, ['/foo/bar/aaa.proto', '/foo/bar/bbb.proto']),
|
||||
).toEqual(
|
||||
[
|
||||
`grpcurl -import-path '/foo/bar'`,
|
||||
`-import-path '/foo'`,
|
||||
`-proto '/foo/bar/aaa.proto'`,
|
||||
`-proto '/foo/bar/bbb.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Multiple proto files, different dir', async () => {
|
||||
expect(
|
||||
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb/ccc.proto', '/xxx/yyy/zzz.proto']),
|
||||
).toEqual(
|
||||
[
|
||||
`grpcurl -import-path '/aaa/bbb'`,
|
||||
`-import-path '/aaa'`,
|
||||
`-import-path '/xxx/yyy'`,
|
||||
`-import-path '/xxx'`,
|
||||
`-proto '/aaa/bbb/ccc.proto'`,
|
||||
`-proto '/xxx/yyy/zzz.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Single include dir', async () => {
|
||||
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb'])).toEqual(
|
||||
[`grpcurl -import-path '/aaa/bbb'`, `yaak.app`].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Multiple include dir', async () => {
|
||||
expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy'])).toEqual(
|
||||
[`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, `yaak.app`].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Mixed proto and dirs', async () => {
|
||||
expect(
|
||||
await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy', '/foo/bar.proto']),
|
||||
).toEqual(
|
||||
[
|
||||
`grpcurl -import-path '/aaa/bbb'`,
|
||||
`-import-path '/xxx/yyy'`,
|
||||
`-import-path '/foo'`,
|
||||
`-import-path '/'`,
|
||||
`-proto '/foo/bar.proto'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Sends data', async () => {
|
||||
expect(
|
||||
await convert(
|
||||
{
|
||||
url: 'https://yaak.app',
|
||||
message: JSON.stringify({ foo: 'bar', baz: 1.0 }, null, 2),
|
||||
},
|
||||
['/foo.proto'],
|
||||
),
|
||||
).toEqual(
|
||||
[
|
||||
`grpcurl -import-path '/'`,
|
||||
`-proto '/foo.proto'`,
|
||||
`-d '{"foo":"bar","baz":1}'`,
|
||||
`yaak.app`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/auth-basic",
|
||||
"displayName": "Basic Authentication",
|
||||
"description": "Authenticate requests using Basic Auth",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
name: 'basic',
|
||||
label: 'Basic Auth',
|
||||
shortLabel: 'Basic',
|
||||
args: [{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
optional: true,
|
||||
}, {
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
optional: true,
|
||||
password: true,
|
||||
}],
|
||||
async onApply(_ctx, { values }) {
|
||||
const { username, password } = values;
|
||||
const value = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
|
||||
return { setHeaders: [{ name: 'Authorization', value }] };
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/auth-bearer",
|
||||
"displayName": "Bearer Authentication",
|
||||
"description": "Authenticate requests using bearer authentication",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
name: 'bearer',
|
||||
label: 'Bearer Token',
|
||||
shortLabel: 'Bearer',
|
||||
args: [{
|
||||
type: 'text',
|
||||
name: 'token',
|
||||
label: 'Token',
|
||||
optional: true,
|
||||
password: true,
|
||||
}],
|
||||
async onApply(_ctx, { values }) {
|
||||
const { token } = values;
|
||||
const value = `Bearer ${token}`.trim();
|
||||
return { setHeaders: [{ name: 'Authorization', value }] };
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/auth-jwt",
|
||||
"displayName": "JWT Authentication",
|
||||
"description": "Authenticate requests using JSON web tokens (JWT)",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.7"
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { PluginDefinition } from '@yaakapp/api';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const algorithms = [
|
||||
'HS256',
|
||||
'HS384',
|
||||
'HS512',
|
||||
'RS256',
|
||||
'RS384',
|
||||
'RS512',
|
||||
'PS256',
|
||||
'PS384',
|
||||
'PS512',
|
||||
'ES256',
|
||||
'ES384',
|
||||
'ES512',
|
||||
'none',
|
||||
] as const;
|
||||
|
||||
const defaultAlgorithm = algorithms[0];
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
name: 'jwt',
|
||||
label: 'JWT Bearer',
|
||||
shortLabel: 'JWT',
|
||||
args: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'algorithm',
|
||||
label: 'Algorithm',
|
||||
hideLabel: true,
|
||||
defaultValue: defaultAlgorithm,
|
||||
options: algorithms.map(value => ({ label: value === 'none' ? 'None' : value, value })),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'secret',
|
||||
label: 'Secret or Private Key',
|
||||
password: true,
|
||||
optional: true,
|
||||
multiLine: true,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'secretBase64',
|
||||
label: 'Secret is base64 encoded',
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'payload',
|
||||
label: 'Payload',
|
||||
language: 'json',
|
||||
defaultValue: '{\n "foo": "bar"\n}',
|
||||
placeholder: '{ }',
|
||||
},
|
||||
],
|
||||
async onApply(_ctx, { values }) {
|
||||
const { algorithm, secret: _secret, secretBase64, payload } = values;
|
||||
const secret = secretBase64 ? Buffer.from(`${_secret}`, 'base64') : `${_secret}`;
|
||||
const token = jwt.sign(`${payload}`, secret, { algorithm: algorithm as any });
|
||||
const value = `Bearer ${token}`;
|
||||
return { setHeaders: [{ name: 'Authorization', value }] };
|
||||
}
|
||||
,
|
||||
},
|
||||
}
|
||||
;
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/auth-oauth2",
|
||||
"displayName": "OAuth 2.0 Authentication",
|
||||
"description": "Authenticate requests using OAuth 2.0",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { Context, HttpRequest, HttpUrlParameter } from '@yaakapp/api';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import type { AccessTokenRawResponse } from './store';
|
||||
|
||||
export async function fetchAccessToken(
|
||||
ctx: Context,
|
||||
{
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
audience,
|
||||
params,
|
||||
grantType,
|
||||
credentialsInBody,
|
||||
clientId,
|
||||
clientSecret,
|
||||
}: {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
grantType: string;
|
||||
accessTokenUrl: string;
|
||||
scope: string | null;
|
||||
audience: string | null;
|
||||
credentialsInBody: boolean;
|
||||
params: HttpUrlParameter[];
|
||||
},
|
||||
): Promise<AccessTokenRawResponse> {
|
||||
console.log('[oauth2] Getting access token', accessTokenUrl);
|
||||
const httpRequest: Partial<HttpRequest> = {
|
||||
method: 'POST',
|
||||
url: accessTokenUrl,
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: [{ name: 'grant_type', value: grantType }, ...params],
|
||||
},
|
||||
headers: [
|
||||
{ name: 'User-Agent', value: 'yaak' },
|
||||
{ name: 'Accept', value: 'application/x-www-form-urlencoded, application/json' },
|
||||
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' },
|
||||
],
|
||||
};
|
||||
|
||||
if (scope) httpRequest.body!.form.push({ name: 'scope', value: scope });
|
||||
if (audience) httpRequest.body!.form.push({ name: 'audience', value: audience });
|
||||
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body!.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body!.form.push({ name: 'client_secret', value: clientSecret });
|
||||
} else {
|
||||
const value = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
console.log('[oauth2] Got access token response', resp.status);
|
||||
|
||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(
|
||||
'Failed to fetch access token with status=' + resp.status + ' and body=' + body,
|
||||
);
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
response = Object.fromEntries(new URLSearchParams(body));
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error('Failed to fetch access token with ' + response.error);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import type { AccessToken } from './store';
|
||||
import { getToken } from './store';
|
||||
|
||||
export async function getAccessTokenIfNotExpired(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
): Promise<AccessToken | null> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null || isTokenExpired(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function isTokenExpired(token: AccessToken) {
|
||||
return token.expiresAt && Date.now() > token.expiresAt;
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import type { Context, HttpRequest } from '@yaakapp/api';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { isTokenExpired } from './getAccessTokenIfNotExpired';
|
||||
import type { AccessToken, AccessTokenRawResponse } from './store';
|
||||
import { deleteToken, getToken, storeToken } from './store';
|
||||
|
||||
export async function getOrRefreshAccessToken(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
{
|
||||
scope,
|
||||
accessTokenUrl,
|
||||
credentialsInBody,
|
||||
clientId,
|
||||
clientSecret,
|
||||
forceRefresh,
|
||||
}: {
|
||||
scope: string | null;
|
||||
accessTokenUrl: string;
|
||||
credentialsInBody: boolean;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
forceRefresh?: boolean;
|
||||
},
|
||||
): Promise<AccessToken | null> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isExpired = isTokenExpired(token);
|
||||
|
||||
// Return the current access token if it's still valid
|
||||
if (!isExpired && !forceRefresh) {
|
||||
return token;
|
||||
}
|
||||
|
||||
// Token is expired, but there's no refresh token :(
|
||||
if (!token.response.refresh_token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Access token is expired, so get a new one
|
||||
const httpRequest: Partial<HttpRequest> = {
|
||||
method: 'POST',
|
||||
url: accessTokenUrl,
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: [
|
||||
{ name: 'grant_type', value: 'refresh_token' },
|
||||
{ name: 'refresh_token', value: token.response.refresh_token },
|
||||
],
|
||||
},
|
||||
headers: [
|
||||
{ name: 'User-Agent', value: 'yaak' },
|
||||
{ name: 'Accept', value: 'application/x-www-form-urlencoded, application/json' },
|
||||
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' },
|
||||
],
|
||||
};
|
||||
|
||||
if (scope) httpRequest.body!.form.push({ name: 'scope', value: scope });
|
||||
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body!.form.push({ name: 'client_id', value: clientId });
|
||||
httpRequest.body!.form.push({ name: 'client_secret', value: clientSecret });
|
||||
} else {
|
||||
const value = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||||
httpRequest.headers!.push({ name: 'Authorization', value });
|
||||
}
|
||||
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
if (resp.status === 401) {
|
||||
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
||||
// and returning null;
|
||||
console.log('[oauth2] Unauthorized refresh_token request');
|
||||
await deleteToken(ctx, contextId);
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||
|
||||
console.log('[oauth2] Got refresh token response', resp.status);
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(
|
||||
'Failed to refresh access token with status=' + resp.status + ' and body=' + body,
|
||||
);
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
response = Object.fromEntries(new URLSearchParams(body));
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(
|
||||
`Failed to fetch access token with ${response.error} -> ${response.error_description}`,
|
||||
);
|
||||
}
|
||||
|
||||
const newResponse: AccessTokenRawResponse = {
|
||||
...response,
|
||||
// Assign a new one or keep the old one,
|
||||
refresh_token: response.refresh_token ?? token.response.refresh_token,
|
||||
};
|
||||
|
||||
return storeToken(ctx, contextId, newResponse);
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||
import type { AccessToken } from '../store';
|
||||
import { getDataDirKey, storeToken } from '../store';
|
||||
|
||||
export const PKCE_SHA256 = 'S256';
|
||||
export const PKCE_PLAIN = 'plain';
|
||||
export const DEFAULT_PKCE_METHOD = PKCE_SHA256;
|
||||
|
||||
export async function getAuthorizationCode(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
{
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
redirectUri,
|
||||
scope,
|
||||
state,
|
||||
audience,
|
||||
credentialsInBody,
|
||||
pkce,
|
||||
tokenName,
|
||||
}: {
|
||||
authorizationUrl: string;
|
||||
accessTokenUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
redirectUri: string | null;
|
||||
scope: string | null;
|
||||
state: string | null;
|
||||
audience: string | null;
|
||||
credentialsInBody: boolean;
|
||||
pkce: {
|
||||
challengeMethod: string;
|
||||
codeVerifier: string;
|
||||
} | null;
|
||||
tokenName: 'access_token' | 'id_token';
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
credentialsInBody,
|
||||
});
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
|
||||
const authorizationUrl = new URL(`${authorizationUrlRaw ?? ''}`);
|
||||
authorizationUrl.searchParams.set('response_type', 'code');
|
||||
authorizationUrl.searchParams.set('client_id', clientId);
|
||||
if (redirectUri) authorizationUrl.searchParams.set('redirect_uri', redirectUri);
|
||||
if (scope) authorizationUrl.searchParams.set('scope', scope);
|
||||
if (state) authorizationUrl.searchParams.set('state', state);
|
||||
if (audience) authorizationUrl.searchParams.set('audience', audience);
|
||||
if (pkce) {
|
||||
authorizationUrl.searchParams.set(
|
||||
'code_challenge',
|
||||
pkceCodeChallenge(pkce.codeVerifier, pkce.challengeMethod),
|
||||
);
|
||||
authorizationUrl.searchParams.set('code_challenge_method', pkce.challengeMethod);
|
||||
}
|
||||
|
||||
const logsEnabled = (await ctx.store.get('enable_logs')) ?? false;
|
||||
const dataDirKey = await getDataDirKey(ctx, contextId);
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
console.log('[oauth2] Authorizing', authorizationUrlStr);
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const code = await new Promise<string>(async (resolve, reject) => {
|
||||
let foundCode = false;
|
||||
const { close } = await ctx.window.openUrl({
|
||||
url: authorizationUrlStr,
|
||||
label: 'oauth-authorization-url',
|
||||
dataDirKey,
|
||||
async onClose() {
|
||||
if (!foundCode) {
|
||||
reject(new Error('Authorization window closed'));
|
||||
}
|
||||
},
|
||||
async onNavigate({ url: urlStr }) {
|
||||
const url = new URL(urlStr);
|
||||
if (logsEnabled) console.log('[oauth2] Navigated to', urlStr);
|
||||
|
||||
if (url.searchParams.has('error')) {
|
||||
close();
|
||||
return reject(new Error(`Failed to authorize: ${url.searchParams.get('error')}`));
|
||||
}
|
||||
|
||||
const code = url.searchParams.get('code');
|
||||
if (!code) {
|
||||
console.log('[oauth2] Code not found');
|
||||
return; // Could be one of many redirects in a chain, so skip it
|
||||
}
|
||||
|
||||
// Close the window here, because we don't need it anymore!
|
||||
foundCode = true;
|
||||
close();
|
||||
resolve(code);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
console.log('[oauth2] Code found');
|
||||
const response = await fetchAccessToken(ctx, {
|
||||
grantType: 'authorization_code',
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
credentialsInBody,
|
||||
params: [
|
||||
{ name: 'code', value: code },
|
||||
...(pkce ? [{ name: 'code_verifier', value: pkce.codeVerifier }] : []),
|
||||
...(redirectUri ? [{ name: 'redirect_uri', value: redirectUri }] : []),
|
||||
],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response, tokenName);
|
||||
}
|
||||
|
||||
export function genPkceCodeVerifier() {
|
||||
return encodeForPkce(randomBytes(32));
|
||||
}
|
||||
|
||||
function pkceCodeChallenge(verifier: string, method: string) {
|
||||
if (method === 'plain') {
|
||||
return verifier;
|
||||
}
|
||||
|
||||
const hash = encodeForPkce(createHash('sha256').update(verifier).digest());
|
||||
return hash
|
||||
.replace(/=/g, '') // Remove padding '='
|
||||
.replace(/\+/g, '-') // Replace '+' with '-'
|
||||
.replace(/\//g, '_'); // Replace '/' with '_'
|
||||
}
|
||||
|
||||
function encodeForPkce(bytes: Buffer) {
|
||||
return bytes
|
||||
.toString('base64')
|
||||
.replace(/=/g, '') // Remove padding '='
|
||||
.replace(/\+/g, '-') // Replace '+' with '-'
|
||||
.replace(/\//g, '_'); // Replace '/' with '_'
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
||||
import { getToken, storeToken } from '../store';
|
||||
|
||||
export async function getClientCredentials(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
{
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
credentialsInBody,
|
||||
}: {
|
||||
accessTokenUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
scope: string | null;
|
||||
audience: string | null;
|
||||
credentialsInBody: boolean;
|
||||
},
|
||||
) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token && !isTokenExpired(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
const response = await fetchAccessToken(ctx, {
|
||||
grantType: 'client_credentials',
|
||||
accessTokenUrl,
|
||||
audience,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsInBody,
|
||||
params: [],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { isTokenExpired } from '../getAccessTokenIfNotExpired';
|
||||
import type { AccessToken, AccessTokenRawResponse} from '../store';
|
||||
import { getToken, storeToken } from '../store';
|
||||
|
||||
export async function getImplicit(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
{
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
responseType,
|
||||
clientId,
|
||||
redirectUri,
|
||||
scope,
|
||||
state,
|
||||
audience,
|
||||
tokenName,
|
||||
}: {
|
||||
authorizationUrl: string;
|
||||
responseType: string;
|
||||
clientId: string;
|
||||
redirectUri: string | null;
|
||||
scope: string | null;
|
||||
state: string | null;
|
||||
audience: string | null;
|
||||
tokenName: 'access_token' | 'id_token';
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token != null && !isTokenExpired(token)) {
|
||||
return token;
|
||||
}
|
||||
|
||||
const authorizationUrl = new URL(`${authorizationUrlRaw ?? ''}`);
|
||||
authorizationUrl.searchParams.set('response_type', 'token');
|
||||
authorizationUrl.searchParams.set('client_id', clientId);
|
||||
if (redirectUri) authorizationUrl.searchParams.set('redirect_uri', redirectUri);
|
||||
if (scope) authorizationUrl.searchParams.set('scope', scope);
|
||||
if (state) authorizationUrl.searchParams.set('state', state);
|
||||
if (audience) authorizationUrl.searchParams.set('audience', audience);
|
||||
if (responseType.includes('id_token')) {
|
||||
authorizationUrl.searchParams.set(
|
||||
'nonce',
|
||||
String(Math.floor(Math.random() * 9999999999999) + 1),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const newToken = await new Promise<AccessToken>(async (resolve, reject) => {
|
||||
let foundAccessToken = false;
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
const { close } = await ctx.window.openUrl({
|
||||
url: authorizationUrlStr,
|
||||
label: 'oauth-authorization-url',
|
||||
async onClose() {
|
||||
if (!foundAccessToken) {
|
||||
reject(new Error('Authorization window closed'));
|
||||
}
|
||||
},
|
||||
async onNavigate({ url: urlStr }) {
|
||||
const url = new URL(urlStr);
|
||||
if (url.searchParams.has('error')) {
|
||||
return reject(Error(`Failed to authorize: ${url.searchParams.get('error')}`));
|
||||
}
|
||||
|
||||
const hash = url.hash.slice(1);
|
||||
const params = new URLSearchParams(hash);
|
||||
|
||||
const accessToken = params.get(tokenName);
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
foundAccessToken = true;
|
||||
|
||||
// Close the window here, because we don't need it anymore
|
||||
close();
|
||||
|
||||
const response = Object.fromEntries(params) as unknown as AccessTokenRawResponse;
|
||||
try {
|
||||
resolve(storeToken(ctx, contextId, response));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return newToken;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import { fetchAccessToken } from '../fetchAccessToken';
|
||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||
import type { AccessToken} from '../store';
|
||||
import { storeToken } from '../store';
|
||||
|
||||
export async function getPassword(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
{
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
username,
|
||||
password,
|
||||
credentialsInBody,
|
||||
audience,
|
||||
scope,
|
||||
}: {
|
||||
accessTokenUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
username: string;
|
||||
password: string;
|
||||
scope: string | null;
|
||||
audience: string | null;
|
||||
credentialsInBody: boolean;
|
||||
},
|
||||
): Promise<AccessToken> {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
credentialsInBody,
|
||||
});
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
|
||||
const response = await fetchAccessToken(ctx, {
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
grantType: 'password',
|
||||
credentialsInBody,
|
||||
params: [
|
||||
{ name: 'username', value: username },
|
||||
{ name: 'password', value: password },
|
||||
],
|
||||
});
|
||||
|
||||
return storeToken(ctx, contextId, response);
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
import type {
|
||||
Context,
|
||||
FormInputSelectOption,
|
||||
GetHttpAuthenticationConfigRequest,
|
||||
JsonPrimitive,
|
||||
PluginDefinition,
|
||||
} from '@yaakapp/api';
|
||||
import {
|
||||
genPkceCodeVerifier,
|
||||
DEFAULT_PKCE_METHOD,
|
||||
getAuthorizationCode,
|
||||
PKCE_PLAIN,
|
||||
PKCE_SHA256,
|
||||
} from './grants/authorizationCode';
|
||||
import { getClientCredentials } from './grants/clientCredentials';
|
||||
import { getImplicit } from './grants/implicit';
|
||||
import { getPassword } from './grants/password';
|
||||
import type { AccessToken } from './store';
|
||||
import { deleteToken, getToken, resetDataDirKey } from './store';
|
||||
|
||||
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
|
||||
|
||||
const grantTypes: FormInputSelectOption[] = [
|
||||
{ label: 'Authorization Code', value: 'authorization_code' },
|
||||
{ label: 'Implicit', value: 'implicit' },
|
||||
{ label: 'Resource Owner Password Credential', value: 'password' },
|
||||
{ label: 'Client Credentials', value: 'client_credentials' },
|
||||
];
|
||||
|
||||
const defaultGrantType = grantTypes[0]!.value;
|
||||
|
||||
function hiddenIfNot(
|
||||
grantTypes: GrantType[],
|
||||
...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]
|
||||
) {
|
||||
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
|
||||
const hasGrantType = grantTypes.find((t) => t === String(values.grantType ?? defaultGrantType));
|
||||
const hasOtherBools = other.every((t) => t(values));
|
||||
const show = hasGrantType && hasOtherBools;
|
||||
return { hidden: !show };
|
||||
};
|
||||
}
|
||||
|
||||
const authorizationUrls = [
|
||||
'https://github.com/login/oauth/authorize',
|
||||
'https://account.box.com/api/oauth2/authorize',
|
||||
'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
'https://api.imgur.com/oauth2/authorize',
|
||||
'https://bitly.com/oauth/authorize',
|
||||
'https://gitlab.example.com/oauth/authorize',
|
||||
'https://medium.com/m/oauth/authorize',
|
||||
'https://public-api.wordpress.com/oauth2/authorize',
|
||||
'https://slack.com/oauth/authorize',
|
||||
'https://todoist.com/oauth/authorize',
|
||||
'https://www.dropbox.com/oauth2/authorize',
|
||||
'https://www.linkedin.com/oauth/v2/authorization',
|
||||
'https://MY_SHOP.myshopify.com/admin/oauth/access_token',
|
||||
'https://appcenter.intuit.com/app/connect/oauth2/authorize',
|
||||
];
|
||||
|
||||
const accessTokenUrls = [
|
||||
'https://github.com/login/oauth/access_token',
|
||||
'https://api-ssl.bitly.com/oauth/access_token',
|
||||
'https://api.box.com/oauth2/token',
|
||||
'https://api.dropboxapi.com/oauth2/token',
|
||||
'https://api.imgur.com/oauth2/token',
|
||||
'https://api.medium.com/v1/tokens',
|
||||
'https://gitlab.example.com/oauth/token',
|
||||
'https://public-api.wordpress.com/oauth2/token',
|
||||
'https://slack.com/api/oauth.access',
|
||||
'https://todoist.com/oauth/access_token',
|
||||
'https://www.googleapis.com/oauth2/v4/token',
|
||||
'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
'https://MY_SHOP.myshopify.com/admin/oauth/authorize',
|
||||
'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
|
||||
];
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
authentication: {
|
||||
name: 'oauth2',
|
||||
label: 'OAuth 2.0',
|
||||
shortLabel: 'OAuth 2',
|
||||
actions: [
|
||||
{
|
||||
label: 'Copy Current Token',
|
||||
async onSelect(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
||||
} else {
|
||||
await ctx.clipboard.copyText(token.response.access_token);
|
||||
await ctx.toast.show({
|
||||
message: 'Token copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Delete Token',
|
||||
async onSelect(ctx, { contextId }) {
|
||||
if (await deleteToken(ctx, contextId)) {
|
||||
await ctx.toast.show({ message: 'Token deleted', color: 'success' });
|
||||
} else {
|
||||
await ctx.toast.show({ message: 'No token to delete', color: 'warning' });
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Clear Window Session',
|
||||
async onSelect(ctx, { contextId }) {
|
||||
await resetDataDirKey(ctx, contextId);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Debug Logs',
|
||||
async onSelect(ctx) {
|
||||
const enableLogs = !(await ctx.store.get('enable_logs'));
|
||||
await ctx.store.set('enable_logs', enableLogs);
|
||||
await ctx.toast.show({
|
||||
message: `Debug logs ${enableLogs ? 'enabled' : 'disabled'}`,
|
||||
color: 'info',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
args: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'grantType',
|
||||
label: 'Grant Type',
|
||||
hideLabel: true,
|
||||
defaultValue: defaultGrantType,
|
||||
options: grantTypes,
|
||||
},
|
||||
|
||||
// Always-present fields
|
||||
{
|
||||
type: 'text',
|
||||
name: 'clientId',
|
||||
label: 'Client ID',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
optional: true,
|
||||
password: true,
|
||||
dynamic: hiddenIfNot(['authorization_code', 'password', 'client_credentials']),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'authorizationUrl',
|
||||
optional: true,
|
||||
label: 'Authorization URL',
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
placeholder: authorizationUrls[0],
|
||||
completionOptions: authorizationUrls.map((url) => ({ label: url, value: url })),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'accessTokenUrl',
|
||||
optional: true,
|
||||
label: 'Access Token URL',
|
||||
placeholder: accessTokenUrls[0],
|
||||
dynamic: hiddenIfNot(['authorization_code', 'password', 'client_credentials']),
|
||||
completionOptions: accessTokenUrls.map((url) => ({ label: url, value: url })),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'redirectUri',
|
||||
label: 'Redirect URI',
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'audience',
|
||||
label: 'Audience',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'tokenName',
|
||||
label: 'Token for authorization',
|
||||
description:
|
||||
'Select which token to send in the "Authorization: Bearer" header. Most APIs expect ' +
|
||||
'access_token, but some (like OpenID Connect) require id_token.',
|
||||
defaultValue: 'access_token',
|
||||
options: [
|
||||
{ label: 'access_token', value: 'access_token' },
|
||||
{ label: 'id_token', value: 'id_token' },
|
||||
],
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'usePkce',
|
||||
label: 'Use PKCE',
|
||||
dynamic: hiddenIfNot(['authorization_code']),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'pkceChallengeMethod',
|
||||
label: 'Code Challenge Method',
|
||||
options: [
|
||||
{ label: 'SHA-256', value: PKCE_SHA256 },
|
||||
{ label: 'Plain', value: PKCE_PLAIN },
|
||||
],
|
||||
defaultValue: DEFAULT_PKCE_METHOD,
|
||||
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'pkceCodeChallenge',
|
||||
label: 'Code Verifier',
|
||||
placeholder: 'Automatically generated when not set',
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['password']),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
password: true,
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['password']),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'responseType',
|
||||
label: 'Response Type',
|
||||
defaultValue: 'token',
|
||||
options: [
|
||||
{ label: 'Access Token', value: 'token' },
|
||||
{ label: 'ID Token', value: 'id_token' },
|
||||
{ label: 'ID and Access Token', value: 'id_token token' },
|
||||
],
|
||||
dynamic: hiddenIfNot(['implicit']),
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Advanced',
|
||||
inputs: [
|
||||
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'headerPrefix',
|
||||
label: 'Header Prefix',
|
||||
optional: true,
|
||||
defaultValue: 'Bearer',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'credentials',
|
||||
label: 'Send Credentials',
|
||||
defaultValue: 'body',
|
||||
options: [
|
||||
{ label: 'In Request Body', value: 'body' },
|
||||
{ label: 'As Basic Authentication', value: 'basic' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Access Token Response',
|
||||
async dynamic(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
return { hidden: true };
|
||||
}
|
||||
return {
|
||||
label: 'Access Token Response',
|
||||
inputs: [
|
||||
{
|
||||
type: 'editor',
|
||||
defaultValue: JSON.stringify(token.response, null, 2),
|
||||
hideLabel: true,
|
||||
readOnly: true,
|
||||
language: 'json',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
async onApply(ctx, { values, contextId }) {
|
||||
const headerPrefix = stringArg(values, 'headerPrefix');
|
||||
const grantType = stringArg(values, 'grantType') as GrantType;
|
||||
const credentialsInBody = values.credentials === 'body';
|
||||
const tokenName = values.tokenName === 'id_token' ? 'id_token' : 'access_token';
|
||||
|
||||
let token: AccessToken;
|
||||
if (grantType === 'authorization_code') {
|
||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
token = await getAuthorizationCode(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
state: stringArgOrNull(values, 'state'),
|
||||
credentialsInBody,
|
||||
pkce: values.usePkce
|
||||
? {
|
||||
challengeMethod: stringArg(values, 'pkceChallengeMethod') || DEFAULT_PKCE_METHOD,
|
||||
codeVerifier: stringArg(values, 'pkceCodeVerifier') || genPkceCodeVerifier(),
|
||||
}
|
||||
: null,
|
||||
tokenName: tokenName,
|
||||
});
|
||||
} else if (grantType === 'implicit') {
|
||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||
token = await getImplicit(ctx, contextId, {
|
||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||
responseType: stringArg(values, 'responseType'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
state: stringArgOrNull(values, 'state'),
|
||||
tokenName: tokenName,
|
||||
});
|
||||
} else if (grantType === 'client_credentials') {
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
token = await getClientCredentials(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
credentialsInBody,
|
||||
});
|
||||
} else if (grantType === 'password') {
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
token = await getPassword(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
username: stringArg(values, 'username'),
|
||||
password: stringArg(values, 'password'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
credentialsInBody,
|
||||
});
|
||||
} else {
|
||||
throw new Error('Invalid grant type ' + grantType);
|
||||
}
|
||||
|
||||
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
||||
return {
|
||||
setHeaders: [
|
||||
{
|
||||
name: 'Authorization',
|
||||
value: headerValue,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function stringArgOrNull(
|
||||
values: Record<string, JsonPrimitive | undefined>,
|
||||
name: string,
|
||||
): string | null {
|
||||
const arg = values[name];
|
||||
if (arg == null || arg == '') return null;
|
||||
return `${arg}`;
|
||||
}
|
||||
|
||||
function stringArg(values: Record<string, JsonPrimitive | undefined>, name: string): string {
|
||||
const arg = stringArgOrNull(values, name);
|
||||
if (!arg) return '';
|
||||
return arg;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Context } from '@yaakapp/api';
|
||||
|
||||
export async function storeToken(
|
||||
ctx: Context,
|
||||
contextId: string,
|
||||
response: AccessTokenRawResponse,
|
||||
tokenName: 'access_token' | 'id_token' = 'access_token',
|
||||
) {
|
||||
if (!response[tokenName]) {
|
||||
throw new Error(`${tokenName} not found in response ${Object.keys(response).join(', ')}`);
|
||||
}
|
||||
|
||||
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1000 : null;
|
||||
const token: AccessToken = {
|
||||
response,
|
||||
expiresAt,
|
||||
};
|
||||
await ctx.store.set<AccessToken>(tokenStoreKey(contextId), token);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function getToken(ctx: Context, contextId: string) {
|
||||
return ctx.store.get<AccessToken>(tokenStoreKey(contextId));
|
||||
}
|
||||
|
||||
export async function deleteToken(ctx: Context, contextId: string) {
|
||||
return ctx.store.delete(tokenStoreKey(contextId));
|
||||
}
|
||||
|
||||
export async function resetDataDirKey(ctx: Context, contextId: string) {
|
||||
const key = new Date().toISOString();
|
||||
return ctx.store.set<string>(dataDirStoreKey(contextId), key);
|
||||
}
|
||||
|
||||
export async function getDataDirKey(ctx: Context, contextId: string) {
|
||||
const key = (await ctx.store.get<string>(dataDirStoreKey(contextId))) ?? 'default';
|
||||
return `${contextId}::${key}`;
|
||||
}
|
||||
|
||||
function tokenStoreKey(contextId: string) {
|
||||
return ['token', contextId].join('::');
|
||||
}
|
||||
|
||||
function dataDirStoreKey(contextId: string) {
|
||||
return ['data_dir', contextId].join('::');
|
||||
}
|
||||
|
||||
export interface AccessToken {
|
||||
response: AccessTokenRawResponse;
|
||||
expiresAt: number | null;
|
||||
}
|
||||
|
||||
export interface AccessTokenRawResponse {
|
||||
access_token: string;
|
||||
id_token?: string;
|
||||
token_type?: string;
|
||||
expires_in?: number;
|
||||
refresh_token?: string;
|
||||
error?: string;
|
||||
error_description?: string;
|
||||
scope?: string;
|
||||
}
|
||||
1544
plugins/exporter-curl/package-lock.json
generated
Normal file
1544
plugins/exporter-curl/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
plugins/exporter-curl/package.json
Normal file
7
plugins/exporter-curl/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "exporter-curl",
|
||||
"version": "0.0.1",
|
||||
"devDependencies": {
|
||||
"vitest": "^1.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,8 @@
|
||||
import type { HttpRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import { HttpRequest } from '../../../src-web/lib/models';
|
||||
|
||||
const NEWLINE = '\\\n ';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
httpRequestActions: [
|
||||
{
|
||||
label: 'Copy as Curl',
|
||||
icon: 'copy',
|
||||
async onSelect(ctx, args) {
|
||||
const rendered_request = await ctx.httpRequest.render({
|
||||
httpRequest: args.httpRequest,
|
||||
purpose: 'preview',
|
||||
});
|
||||
const data = await convertToCurl(rendered_request);
|
||||
await ctx.clipboard.copyText(data);
|
||||
await ctx.toast.show({
|
||||
message: 'Command copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export async function convertToCurl(request: Partial<HttpRequest>) {
|
||||
export function pluginHookExport(request: Partial<HttpRequest>) {
|
||||
const xs = ['curl'];
|
||||
|
||||
// Add method and URL all on first line
|
||||
@@ -58,15 +36,9 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
|
||||
}
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
} else if (typeof request.body?.query === 'string') {
|
||||
const body = {
|
||||
query: request.body.query || '',
|
||||
variables: maybeParseJSON(request.body.variables, undefined),
|
||||
};
|
||||
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)}`);
|
||||
// --data-raw $'...' to do special ANSI C quoting
|
||||
xs.push('--data-raw', `$${quote(request.body.text)}`);
|
||||
xs.push(NEWLINE);
|
||||
}
|
||||
|
||||
@@ -102,11 +74,3 @@ function quote(arg: string): string {
|
||||
function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean {
|
||||
return v.enabled !== false && !!v.name;
|
||||
}
|
||||
|
||||
function maybeParseJSON<T>(v: string, fallback: T) {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { convertToCurl } from '../src';
|
||||
import { pluginHookExport } from '../src';
|
||||
|
||||
describe('exporter-curl', () => {
|
||||
test('Exports GET with params', async () => {
|
||||
test('Exports GET with params', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
urlParameters: [
|
||||
{ name: 'a', value: 'aaa' },
|
||||
@@ -16,9 +16,9 @@ describe('exporter-curl', () => {
|
||||
[`curl 'https://yaak.app'`, `--url-query 'a=aaa'`, `--url-query 'b=bbb'`].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
test('Exports POST with url form data', async () => {
|
||||
test('Exports POST with url form data', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
@@ -35,40 +35,9 @@ describe('exporter-curl', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports POST with GraphQL data', async () => {
|
||||
test('Exports PUT with multipart form', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'graphql',
|
||||
body: {
|
||||
query: '{foo,bar}',
|
||||
variables: '{"a": "aaa", "b": "bbb"}',
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl -X POST 'https://yaak.app'`, `--data-raw '{"query":"{foo,bar}","variables":{"a":"aaa","b":"bbb"}}'`].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports POST with GraphQL data no variables', async () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'graphql',
|
||||
body: {
|
||||
query: '{foo,bar}',
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
[`curl -X POST 'https://yaak.app'`, `--data-raw '{"query":"{foo,bar}"}'`].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports PUT with multipart form', async () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'PUT',
|
||||
bodyType: 'multipart/form-data',
|
||||
@@ -91,9 +60,9 @@ describe('exporter-curl', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports JSON body', async () => {
|
||||
test('Exports JSON body', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/json',
|
||||
@@ -106,14 +75,14 @@ describe('exporter-curl', () => {
|
||||
[
|
||||
`curl -X POST 'https://yaak.app'`,
|
||||
`--header 'Content-Type: application/json'`,
|
||||
`--data-raw '{"foo":"bar\\'s"}'`,
|
||||
`--data-raw $'{"foo":"bar\\'s"}'`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports multi-line JSON body', async () => {
|
||||
test('Exports multi-line JSON body', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/json',
|
||||
@@ -126,14 +95,14 @@ describe('exporter-curl', () => {
|
||||
[
|
||||
`curl -X POST 'https://yaak.app'`,
|
||||
`--header 'Content-Type: application/json'`,
|
||||
`--data-raw '{"foo":"bar",\n"baz":"qux"}'`,
|
||||
`--data-raw $'{"foo":"bar",\n"baz":"qux"}'`,
|
||||
].join(` \\\n `),
|
||||
);
|
||||
});
|
||||
|
||||
test('Exports headers', async () => {
|
||||
test('Exports headers', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
headers: [
|
||||
{ name: 'a', value: 'aaa' },
|
||||
{ name: 'b', value: 'bbb', enabled: true },
|
||||
@@ -143,9 +112,9 @@ describe('exporter-curl', () => {
|
||||
).toEqual([`curl`, `--header 'a: aaa'`, `--header 'b: bbb'`].join(` \\\n `));
|
||||
});
|
||||
|
||||
test('Basic auth', async () => {
|
||||
test('Basic auth', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'basic',
|
||||
authentication: {
|
||||
@@ -156,9 +125,9 @@ describe('exporter-curl', () => {
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(` \\\n `));
|
||||
});
|
||||
|
||||
test('Broken basic auth', async () => {
|
||||
test('Broken basic auth', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'basic',
|
||||
authentication: {},
|
||||
@@ -166,9 +135,9 @@ describe('exporter-curl', () => {
|
||||
).toEqual([`curl 'https://yaak.app'`, `--user ':'`].join(` \\\n `));
|
||||
});
|
||||
|
||||
test('Digest auth', async () => {
|
||||
test('Digest auth', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'digest',
|
||||
authentication: {
|
||||
@@ -179,9 +148,9 @@ describe('exporter-curl', () => {
|
||||
).toEqual([`curl 'https://yaak.app'`, `--digest --user 'user:pass'`].join(` \\\n `));
|
||||
});
|
||||
|
||||
test('Bearer auth', async () => {
|
||||
test('Bearer auth', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'bearer',
|
||||
authentication: {
|
||||
@@ -191,9 +160,9 @@ describe('exporter-curl', () => {
|
||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer tok'`].join(` \\\n `));
|
||||
});
|
||||
|
||||
test('Broken bearer auth', async () => {
|
||||
test('Broken bearer auth', () => {
|
||||
expect(
|
||||
await convertToCurl({
|
||||
pluginHookExport({
|
||||
url: 'https://yaak.app',
|
||||
authenticationType: 'bearer',
|
||||
authentication: {
|
||||
13
plugins/exporter-curl/vite.config.js
Normal file
13
plugins/exporter-curl/vite.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/exporter-curl'),
|
||||
},
|
||||
});
|
||||
173
plugins/filter-jsonpath/package-lock.json
generated
Normal file
173
plugins/filter-jsonpath/package-lock.json
generated
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"name": "filter-jsonpath",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "filter-jsonpath",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"jsonpath": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
|
||||
},
|
||||
"node_modules/escodegen": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
|
||||
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
|
||||
"dependencies": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"optionator": "^0.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"escodegen": "bin/escodegen.js",
|
||||
"esgenerate": "bin/esgenerate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/escodegen/node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz",
|
||||
"integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
|
||||
},
|
||||
"node_modules/jsonpath": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz",
|
||||
"integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==",
|
||||
"dependencies": {
|
||||
"esprima": "1.2.2",
|
||||
"static-eval": "2.0.2",
|
||||
"underscore": "1.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
|
||||
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
|
||||
"dependencies": {
|
||||
"deep-is": "~0.1.3",
|
||||
"fast-levenshtein": "~2.0.6",
|
||||
"levn": "~0.3.0",
|
||||
"prelude-ls": "~1.1.2",
|
||||
"type-check": "~0.3.2",
|
||||
"word-wrap": "~1.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/static-eval": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz",
|
||||
"integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==",
|
||||
"dependencies": {
|
||||
"escodegen": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
|
||||
"dependencies": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw=="
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,7 @@
|
||||
{
|
||||
"name": "@yaak/filter-jsonpath",
|
||||
"displayName": "JSONPath Filter",
|
||||
"description": "Filter JSON response data using JSONPath expressions",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"name": "filter-jsonpath",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonpath": "^0.2.4"
|
||||
"jsonpath": "^1.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
12
plugins/filter-jsonpath/src/index.js
Normal file
12
plugins/filter-jsonpath/src/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import jp from 'jsonpath';
|
||||
|
||||
export function pluginHookResponseFilter(filter, text) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
const filtered = jp.query(parsed, filter);
|
||||
return { filtered: JSON.stringify(filtered, null, 2) };
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
filter: {
|
||||
name: 'JSONPath',
|
||||
description: 'Filter JSONPath',
|
||||
onFilter(_ctx, args) {
|
||||
const parsed = JSON.parse(args.payload);
|
||||
try {
|
||||
const filtered = JSONPath({ path: args.filter, json: parsed });
|
||||
return { content: JSON.stringify(filtered, null, 2) };
|
||||
} catch (err) {
|
||||
return { content: '', error: `Invalid filter: ${err}` };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
13
plugins/filter-jsonpath/vite.config.js
Normal file
13
plugins/filter-jsonpath/vite.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.js'),
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-jsonpath'),
|
||||
},
|
||||
});
|
||||
32
plugins/filter-xpath/package-lock.json
generated
Normal file
32
plugins/filter-xpath/package-lock.json
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "filter-xpath",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "filter-xpath",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"xpath": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xpath": {
|
||||
"version": "0.0.34",
|
||||
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz",
|
||||
"integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==",
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"name": "@yaak/filter-xpath",
|
||||
"displayName": "XPath Filter",
|
||||
"description": "Filter response XML data using XPath expressions",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"name": "filter-xpath",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"xpath": "^0.0.34"
|
||||
|
||||
8
plugins/filter-xpath/src/index.js
Normal file
8
plugins/filter-xpath/src/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import xpath from 'xpath';
|
||||
import { DOMParser } from '@xmldom/xmldom';
|
||||
|
||||
export function pluginHookResponseFilter(filter, text) {
|
||||
const doc = new DOMParser().parseFromString(text, 'text/xml');
|
||||
const filtered = `${xpath.select(filter, doc)}`;
|
||||
return { filtered };
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { DOMParser } from '@xmldom/xmldom';
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import xpath from 'xpath';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
filter: {
|
||||
name: 'XPath',
|
||||
description: 'Filter XPath',
|
||||
onFilter(_ctx, args) {
|
||||
const doc = new DOMParser().parseFromString(args.payload, 'text/xml');
|
||||
try {
|
||||
const result = xpath.select(args.filter, doc, false);
|
||||
if (Array.isArray(result)) {
|
||||
return { content: result.map((r) => String(r)).join('\n') };
|
||||
} else {
|
||||
// Not sure what cases this happens in (?)
|
||||
return { content: String(result) };
|
||||
}
|
||||
} catch (err) {
|
||||
return { content: '', error: `Invalid filter: ${err}` };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
13
plugins/filter-xpath/vite.config.js
Normal file
13
plugins/filter-xpath/vite.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.js'),
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: resolve(__dirname, '../../src-tauri/plugins/filter-xpath'),
|
||||
},
|
||||
});
|
||||
1562
plugins/importer-curl/package-lock.json
generated
Normal file
1562
plugins/importer-curl/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user