mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-16 07:37:48 +01:00
Compare commits
649 Commits
v2024.7.0-
...
v2024.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cefdc3ecf3 | ||
|
|
02960d2d64 | ||
|
|
9e5226aa83 | ||
|
|
63d7a44586 | ||
|
|
c851dfe206 | ||
|
|
6adc15a249 | ||
|
|
9ac7aac296 | ||
|
|
325d63e1b7 | ||
|
|
e639a77165 | ||
|
|
c075efc752 | ||
|
|
c4f42f71c3 | ||
|
|
535adfe200 | ||
|
|
85fa159f0d | ||
|
|
fd2fe46c95 | ||
|
|
6e52f35626 | ||
|
|
a0d1e7023d | ||
|
|
97a2f00d59 | ||
|
|
50ad4efad7 | ||
|
|
79a3d9c8df | ||
|
|
b8e20d885f | ||
|
|
752eb3dbd5 | ||
|
|
616acdfb56 | ||
|
|
b2bcbababe | ||
|
|
9f5a3ef96a | ||
|
|
d2c5bdc3c8 | ||
|
|
0d6899a12c | ||
|
|
1b25cb0c4c | ||
|
|
783b7222df | ||
|
|
ff3165ab30 | ||
|
|
9780dc88a1 | ||
|
|
e4f0d2a341 | ||
|
|
bcf0ae159d | ||
|
|
5664d41073 | ||
|
|
e75e6865ea | ||
|
|
fd5b495b70 | ||
|
|
16506d1ddd | ||
|
|
e3016f7100 | ||
|
|
766da4327c | ||
|
|
6f389b0010 | ||
|
|
007ea88edd | ||
|
|
5409678855 | ||
|
|
4c6bd63b8b | ||
|
|
8db80d2e97 | ||
|
|
c80fca8063 | ||
|
|
7384398813 | ||
|
|
b57ea8adeb | ||
|
|
8ff2caf3c3 | ||
|
|
a521b8f308 | ||
|
|
50ba167516 | ||
|
|
cb102657ea | ||
|
|
a7d9e2432b | ||
|
|
d842b168e6 | ||
|
|
870cb25980 | ||
|
|
fde0c5540b | ||
|
|
2ec9a1c19d | ||
|
|
c2f5a3bf45 | ||
|
|
7c18eeae8c | ||
|
|
d7a1b4b7bc | ||
|
|
4566ede184 | ||
|
|
f45c898be0 | ||
|
|
4e1700f8a4 | ||
|
|
f14311d14a | ||
|
|
470a7e2278 | ||
|
|
2d67be481d | ||
|
|
9f6ddb1558 | ||
|
|
853f07b9af | ||
|
|
0eb6358387 | ||
|
|
d43e045f25 | ||
|
|
17432fca29 | ||
|
|
d5931660c2 | ||
|
|
cd7678b7a1 | ||
|
|
706be1188b | ||
|
|
8989b61a13 | ||
|
|
a997944f16 | ||
|
|
f8e8f5d3f2 | ||
|
|
812e5238ac | ||
|
|
16d4f2952d | ||
|
|
ac9a6d5871 | ||
|
|
4fcf1df61f | ||
|
|
394beb374e | ||
|
|
ba4d1063e3 | ||
|
|
2bd9b436e6 | ||
|
|
915a59dec4 | ||
|
|
ae2b746cb2 | ||
|
|
b04cff153b | ||
|
|
bd8e71e567 | ||
|
|
562a36d616 | ||
|
|
c85a11edf1 | ||
|
|
ef7f942a8f | ||
|
|
a7f2a86d71 | ||
|
|
bf90f84d16 | ||
|
|
4284aa2549 | ||
|
|
60773cab53 | ||
|
|
e2c17873ae | ||
|
|
88982156ee | ||
|
|
722c8a1c6b | ||
|
|
8c15274786 | ||
|
|
1abba4980a | ||
|
|
3a340999ec | ||
|
|
b7261e77aa | ||
|
|
23431b40e9 | ||
|
|
04f31cd4a7 | ||
|
|
7d82a7e74a | ||
|
|
d31255d987 | ||
|
|
e53693f605 | ||
|
|
67aa7b7268 | ||
|
|
e27ed9becc | ||
|
|
22d21af3c2 | ||
|
|
67000af7f9 | ||
|
|
b84c7ba50c | ||
|
|
a0b3f86462 | ||
|
|
6a8395660d | ||
|
|
2c041fbac6 | ||
|
|
1eed0e8f22 | ||
|
|
63a0ed273d | ||
|
|
d0be5ca515 | ||
|
|
b964c942d6 | ||
|
|
a05fc5fd20 | ||
|
|
de183abd24 | ||
|
|
5c44df7b00 | ||
|
|
dbdce4cf9a | ||
|
|
219a6b78da | ||
|
|
fb11aff03f | ||
|
|
15714ae188 | ||
|
|
ce116d032d | ||
|
|
6f41df6e52 | ||
|
|
0853d2ca95 | ||
|
|
6798331ce5 | ||
|
|
5ffc75e0ad | ||
|
|
bf92371a49 | ||
|
|
bd3da86317 | ||
|
|
3db3d42246 | ||
|
|
de8bf3ca70 | ||
|
|
8bc131de6c | ||
|
|
efce69292d | ||
|
|
0ccc893440 | ||
|
|
1f9756c917 | ||
|
|
be8f0e4521 | ||
|
|
bcdf51d231 | ||
|
|
1a1553eebd | ||
|
|
321c3862fe | ||
|
|
466d412e65 | ||
|
|
86f50b826f | ||
|
|
ac1e646e68 | ||
|
|
33374eefc7 | ||
|
|
7047df4f7e | ||
|
|
c8bd4d0ae0 | ||
|
|
1e79f76701 | ||
|
|
18852dca06 | ||
|
|
408e7e80b7 | ||
|
|
fc185de023 | ||
|
|
bb9d3a42f3 | ||
|
|
baf0f4291d | ||
|
|
536066142c | ||
|
|
04cf16497d | ||
|
|
feb5972090 | ||
|
|
77bf5a58d8 | ||
|
|
3539642491 | ||
|
|
08abea6a6f | ||
|
|
0045b85f00 | ||
|
|
4b34c3d101 | ||
|
|
4af0a15d9f | ||
|
|
3a4a76c58d | ||
|
|
3086d815c1 | ||
|
|
a48a9eab4a | ||
|
|
48664c66e5 | ||
|
|
7aee5176a9 | ||
|
|
0da68ced18 | ||
|
|
39f7d9c113 | ||
|
|
138943bfb6 | ||
|
|
c1c9f882a6 | ||
|
|
1bcf26f656 | ||
|
|
7c2466da5e | ||
|
|
7dc78a1f6f | ||
|
|
88d024023b | ||
|
|
626aacf982 | ||
|
|
d5855c45a6 | ||
|
|
793bff9f27 | ||
|
|
88ea68e72f | ||
|
|
35e40d2c55 | ||
|
|
c472b83409 | ||
|
|
52c26d235c | ||
|
|
ac54729012 | ||
|
|
0586034ef4 | ||
|
|
91790ba708 | ||
|
|
d8ab6c0b50 | ||
|
|
b600a21a2b | ||
|
|
4f9d1278f7 | ||
|
|
15aa93f5f9 | ||
|
|
c7798092d8 | ||
|
|
5560593aaa | ||
|
|
66639e651d | ||
|
|
8e42d5ccdb | ||
|
|
5c62594087 | ||
|
|
26b6c48657 | ||
|
|
0290aba982 | ||
|
|
0bafc4e4f5 | ||
|
|
9a36f94279 | ||
|
|
1d8e66179e | ||
|
|
fda6d16d8e | ||
|
|
c4737916df | ||
|
|
919465cdbb | ||
|
|
de3730fa4f | ||
|
|
aff26fdd46 | ||
|
|
3c0edf06af | ||
|
|
cb8939db88 | ||
|
|
bf4b3213c4 | ||
|
|
633d7c52c4 | ||
|
|
0401cb92aa | ||
|
|
bff6c668a0 | ||
|
|
ee87e65763 | ||
|
|
f165a0b827 | ||
|
|
f7426dc8ce | ||
|
|
6114039f7e | ||
|
|
da414debe1 | ||
|
|
11f5541558 | ||
|
|
1bc155d684 | ||
|
|
335231060e | ||
|
|
0fdf64440f | ||
|
|
a984fb33dc | ||
|
|
41b1ec96c9 | ||
|
|
df83a61d6f | ||
|
|
d289f1fd13 | ||
|
|
aea4e961aa | ||
|
|
c554b73d48 | ||
|
|
b519bff3d6 | ||
|
|
8381104302 | ||
|
|
5ef7c6a1a2 | ||
|
|
6d7a81850c | ||
|
|
6e5d5fcb95 | ||
|
|
004fef6729 | ||
|
|
0bec5a6405 | ||
|
|
60b091ff1c | ||
|
|
bee1a5cb2d | ||
|
|
bb2d3dd5b1 | ||
|
|
bf8aad04c7 | ||
|
|
4306294a72 | ||
|
|
10f3722fe3 | ||
|
|
c1af9ca44a | ||
|
|
5b230c74f0 | ||
|
|
5cebb4e61a | ||
|
|
bd9d1e2244 | ||
|
|
9bdaa05f00 | ||
|
|
750ad0c902 | ||
|
|
a9c16838e6 | ||
|
|
d5065ab6d9 | ||
|
|
9ebb3ef532 | ||
|
|
aeda72f13e | ||
|
|
83aa9041cb | ||
|
|
d51913509d | ||
|
|
5106f28ba5 | ||
|
|
0c55c6eaab | ||
|
|
b0edbd19c8 | ||
|
|
7630db79b7 | ||
|
|
55a7b82567 | ||
|
|
b5cb46918a | ||
|
|
a793ece1a5 | ||
|
|
0f6e4b641a | ||
|
|
5ac5fab0c6 | ||
|
|
8030a8a235 | ||
|
|
d98426cad3 | ||
|
|
06034a8fc4 | ||
|
|
1ee9f9bb51 | ||
|
|
4b99d1405e | ||
|
|
8480e52195 | ||
|
|
243e65a992 | ||
|
|
b82304a233 | ||
|
|
f7a4ea9735 | ||
|
|
33d1a84ecd | ||
|
|
f4a071ee05 | ||
|
|
e26ba0f9d0 | ||
|
|
b4e2a12375 | ||
|
|
5e7aacd31a | ||
|
|
00718df49e | ||
|
|
bb9025ab07 | ||
|
|
867f3908ed | ||
|
|
30e1ecac39 | ||
|
|
7eb2abe9b2 | ||
|
|
a5ac8fa035 | ||
|
|
dd705de155 | ||
|
|
b15cdec701 | ||
|
|
a99a36b5cc | ||
|
|
e0b0e3d781 | ||
|
|
98a4834d4f | ||
|
|
32b135dbaf | ||
|
|
0fc8d12a06 | ||
|
|
3c2bdab101 | ||
|
|
8b5d7ae3ed | ||
|
|
51949f4fbf | ||
|
|
6013cd2329 | ||
|
|
eba28ade48 | ||
|
|
44af1ddc8a | ||
|
|
63c0d09df8 | ||
|
|
f305633d94 | ||
|
|
13155f8591 | ||
|
|
f2ac97aa62 | ||
|
|
18eb0027a1 | ||
|
|
9e2803fcfb | ||
|
|
705e30b6e0 | ||
|
|
f1260911ea | ||
|
|
076ff63dbe | ||
|
|
899092b4d2 | ||
|
|
c2c3a28aab | ||
|
|
25c0db502e | ||
|
|
6dcbe45a53 | ||
|
|
e2b46f25ff | ||
|
|
981182be46 | ||
|
|
ad164ebd5e | ||
|
|
cacdad8826 | ||
|
|
77e5142a7c | ||
|
|
613081728d | ||
|
|
23e77dfec1 | ||
|
|
6e273ae2a3 | ||
|
|
4061094988 | ||
|
|
82b185e27f | ||
|
|
27dc261639 | ||
|
|
7e45fecf19 | ||
|
|
1a5053380b | ||
|
|
408665c62d | ||
|
|
65efee2048 | ||
|
|
3faa66a1fc | ||
|
|
9dafe4f704 | ||
|
|
356eaf1713 | ||
|
|
f8584f1537 | ||
|
|
6ad6cb34b0 | ||
|
|
32b27cd780 | ||
|
|
0344a1e8c9 | ||
|
|
0515271c12 | ||
|
|
5ae8d54ce0 | ||
|
|
33c406ce49 | ||
|
|
3b660ddbd0 | ||
|
|
3132728a27 | ||
|
|
7063128342 | ||
|
|
2187775462 | ||
|
|
18adcd1004 | ||
|
|
b0656d1e38 | ||
|
|
38e66047e0 | ||
|
|
c24f049dac | ||
|
|
53d13c8172 | ||
|
|
0727c6e437 | ||
|
|
8328d20150 | ||
|
|
afe6a3bf57 | ||
|
|
d920632cbd | ||
|
|
5c456fd4d5 | ||
|
|
38c247e350 | ||
|
|
0c8f72124a | ||
|
|
80ed6b1525 | ||
|
|
4424b3f208 | ||
|
|
2c75abce09 | ||
|
|
4e15eb197f | ||
|
|
a7544b4f8c | ||
|
|
d126aad172 | ||
|
|
acc5c0de50 | ||
|
|
3391da111d | ||
|
|
e37ce96956 | ||
|
|
c51831c975 | ||
|
|
180aa39de4 | ||
|
|
3bd780782e | ||
|
|
f9ba2f79c2 | ||
|
|
d9493de2be | ||
|
|
bc9a623742 | ||
|
|
532edbf274 | ||
|
|
1585692328 | ||
|
|
083f565b12 | ||
|
|
f7f7438c9e | ||
|
|
19934a93bb | ||
|
|
577cfe5bdc | ||
|
|
43ac6afae1 | ||
|
|
8cc11703d3 | ||
|
|
4f7a116378 | ||
|
|
513793d9ce | ||
|
|
67f32b6734 | ||
|
|
66813d67fe | ||
|
|
a38691ed53 | ||
|
|
deeefdcfbf | ||
|
|
db292511b1 | ||
|
|
1a5334c1ce | ||
|
|
11002abe39 | ||
|
|
d922dcb062 | ||
|
|
6fcaa18e86 | ||
|
|
7664c941dd | ||
|
|
6f5cb528c6 | ||
|
|
ebb78922f0 | ||
|
|
2285fe9f1c | ||
|
|
38ba8625d8 | ||
|
|
ab5681c7ad | ||
|
|
f66dcb9267 | ||
|
|
1b6cfbac77 | ||
|
|
4c27e788ea | ||
|
|
769da0b052 | ||
|
|
6b60c86300 | ||
|
|
30c1b5e8c7 | ||
|
|
10af9b6f99 | ||
|
|
aa8c066f2d | ||
|
|
b913b74449 | ||
|
|
b71adce50b | ||
|
|
0fbb44c701 | ||
|
|
de335e8637 | ||
|
|
2999f63a4c | ||
|
|
2abc5e6f0b | ||
|
|
639de4321e | ||
|
|
b3c461afdd | ||
|
|
7d154800a0 | ||
|
|
b48ed0399e | ||
|
|
c5d6e7d74a | ||
|
|
e82f915363 | ||
|
|
3128e9ce76 | ||
|
|
bc0e86757c | ||
|
|
fec99916c2 | ||
|
|
3b5d059b11 | ||
|
|
c3fe2acc8a | ||
|
|
4d002c412b | ||
|
|
46d152b5f1 | ||
|
|
25fa81ebbc | ||
|
|
7c2de3c360 | ||
|
|
3a3b187cd0 | ||
|
|
3226bbe083 | ||
|
|
a1e4e0e6c9 | ||
|
|
b3aa8b893b | ||
|
|
f057139634 | ||
|
|
71a2b11ab4 | ||
|
|
587254a0e7 | ||
|
|
9f4de66f3c | ||
|
|
b0d8908724 | ||
|
|
15c22d98c6 | ||
|
|
3105ae0edc | ||
|
|
11a89f06c1 | ||
|
|
9cbe24e740 | ||
|
|
bfbed13b8f | ||
|
|
2268de6321 | ||
|
|
dd99aa7fcd | ||
|
|
be436bb706 | ||
|
|
bd48726f44 | ||
|
|
10bea83f98 | ||
|
|
8122b4fb84 | ||
|
|
3ae57fb2d8 | ||
|
|
6dc3eecca4 | ||
|
|
9d1d732154 | ||
|
|
8a117415b7 | ||
|
|
d36623ebc9 | ||
|
|
94a3ae3696 | ||
|
|
2836a28988 | ||
|
|
946d7dc89e | ||
|
|
af6300f18b | ||
|
|
905cb4b18e | ||
|
|
305ed09547 | ||
|
|
643356bad3 | ||
|
|
e458675627 | ||
|
|
91e3853692 | ||
|
|
5f0876a136 | ||
|
|
3a38127fb4 | ||
|
|
f3b6070235 | ||
|
|
5e6e78eb9e | ||
|
|
9b66a1d1a8 | ||
|
|
e954d0d7bc | ||
|
|
dab2df7e79 | ||
|
|
bc40e22008 | ||
|
|
eef262c398 | ||
|
|
8eab6e14db | ||
|
|
ded33a110a | ||
|
|
e448a7602a | ||
|
|
4c22215ca5 | ||
|
|
4f501abb72 | ||
|
|
b2dcc38982 | ||
|
|
11b719955b | ||
|
|
d563ac63db | ||
|
|
6d826064c6 | ||
|
|
d30b9d6518 | ||
|
|
8da3364d0f | ||
|
|
07c372b7f5 | ||
|
|
7e01f38253 | ||
|
|
ba637009a7 | ||
|
|
da7388e510 | ||
|
|
3ec88fc896 | ||
|
|
1c9381b2bd | ||
|
|
06349b8d5b | ||
|
|
6dc7dc6ad2 | ||
|
|
f981a15ec3 | ||
|
|
8b648c0301 | ||
|
|
83ce09075b | ||
|
|
168dfb9f6b | ||
|
|
9b8961c23d | ||
|
|
89bca42ee6 | ||
|
|
07d2a43a17 | ||
|
|
c84f2afd09 | ||
|
|
df4dbaecc8 | ||
|
|
d9bf03cefe | ||
|
|
39223e8d89 | ||
|
|
67925e18b2 | ||
|
|
89ad65513d | ||
|
|
90166ddfa3 | ||
|
|
0981b23faf | ||
|
|
664f3b4d87 | ||
|
|
dc97b91a4e | ||
|
|
d310272d19 | ||
|
|
f1be3f01e1 | ||
|
|
c57b6e1d73 | ||
|
|
a938dc45f0 | ||
|
|
bb139744a1 | ||
|
|
3aa3e09552 | ||
|
|
74abfd21b8 | ||
|
|
e703817ba2 | ||
|
|
80dd1e457b | ||
|
|
ea9f8d3ab2 | ||
|
|
fa222bdf12 | ||
|
|
45b360dabd | ||
|
|
5923399359 | ||
|
|
f4600f3e90 | ||
|
|
f883837685 | ||
|
|
b58bc409f0 | ||
|
|
e893e539bb | ||
|
|
90294fbb5d | ||
|
|
ae65f222bc | ||
|
|
1b9813fb4c | ||
|
|
b708b5ae41 | ||
|
|
df136fa915 | ||
|
|
f8329f5b8d | ||
|
|
21141090de | ||
|
|
c0d9740a7d | ||
|
|
afcf630443 | ||
|
|
1fe2c9826a | ||
|
|
7272b80a3f | ||
|
|
92114b7368 | ||
|
|
f39d3e7eed | ||
|
|
cbe0d27a5e | ||
|
|
cd39699467 | ||
|
|
b3ea67aacf | ||
|
|
db4ed9797c | ||
|
|
1ea7d7d685 | ||
|
|
2df725b57a | ||
|
|
74e6648249 | ||
|
|
1026350d9c | ||
|
|
98fb87874d | ||
|
|
41fc3afdc1 | ||
|
|
83dbf46ba4 | ||
|
|
0b2e35bdde | ||
|
|
d90a7331c9 | ||
|
|
264e64a996 | ||
|
|
8915915c47 | ||
|
|
951ed787fa | ||
|
|
64ef6b0c22 | ||
|
|
ef18377b3c | ||
|
|
5904b6fded | ||
|
|
f4401e77bb | ||
|
|
efa5455a7b | ||
|
|
619c8d9e72 | ||
|
|
bdf89ac288 | ||
|
|
debd3c8185 | ||
|
|
f81a3ae8e7 | ||
|
|
7d4e9894c3 | ||
|
|
4bf22d8a60 | ||
|
|
8be4971a23 | ||
|
|
359e916b73 | ||
|
|
68058f3e41 | ||
|
|
0c6fa3e634 | ||
|
|
0fa25c6335 | ||
|
|
5684479f1d | ||
|
|
2d1603601c | ||
|
|
f5394b2210 | ||
|
|
833db5df06 | ||
|
|
525ac7e980 | ||
|
|
44a747c80a | ||
|
|
2056e7f40a | ||
|
|
9b6c1ad364 | ||
|
|
34987bcacb | ||
|
|
b62c11222a | ||
|
|
b3cee3ace3 | ||
|
|
222c054c95 | ||
|
|
46f18a2491 | ||
|
|
f2ca8e2753 | ||
|
|
b0d243c378 | ||
|
|
6161fb86c8 | ||
|
|
b09cc91fe5 | ||
|
|
ef1638cbb3 | ||
|
|
00ef8743f2 | ||
|
|
68222659e3 | ||
|
|
69420a4bba | ||
|
|
0161bbaeb1 | ||
|
|
948dbfe3cc | ||
|
|
338ba8b189 | ||
|
|
ca4655b441 | ||
|
|
bf37499428 | ||
|
|
0b94b57e2a | ||
|
|
fc40aead98 | ||
|
|
7d7f934e6a | ||
|
|
d5fbf4d622 | ||
|
|
e4f6c919dc | ||
|
|
4d806ff2b1 | ||
|
|
bf8f12274f | ||
|
|
f4f438d9fe | ||
|
|
2434f373be | ||
|
|
2bb2061f97 | ||
|
|
2c011a5c2a | ||
|
|
f66b0ccea1 | ||
|
|
665dd8447d | ||
|
|
1b61ce31e6 | ||
|
|
ef4d960698 | ||
|
|
b6d557b632 | ||
|
|
b700bd356c | ||
|
|
620dd7d3ef | ||
|
|
6575121902 | ||
|
|
7c1755a0dc | ||
|
|
8ad301a666 | ||
|
|
ae24cd4939 | ||
|
|
7152e1845e | ||
|
|
96c1dd4081 | ||
|
|
87c7b3a663 | ||
|
|
c1be46a539 | ||
|
|
4655e0018b | ||
|
|
da5ba2e3be | ||
|
|
aaf95f565f | ||
|
|
f32b984e77 | ||
|
|
548aa4c7cd | ||
|
|
0ccceaac77 | ||
|
|
70f534f1d8 | ||
|
|
61fe95b300 | ||
|
|
915e0e8613 | ||
|
|
aace2580da | ||
|
|
3d36905664 | ||
|
|
0d671423da | ||
|
|
aebfcb9437 | ||
|
|
be7ef7beb1 | ||
|
|
d77ed0c5cc | ||
|
|
e57e7bcec5 | ||
|
|
a637842ce4 | ||
|
|
fc54ec49af | ||
|
|
5c43d8510a | ||
|
|
83f84ded8d | ||
|
|
5658da34a2 | ||
|
|
38e8ef6535 | ||
|
|
8c89b06238 | ||
|
|
d85c021305 | ||
|
|
83bb18df03 | ||
|
|
93105a3e89 | ||
|
|
ba3b899115 | ||
|
|
fcfbc1d1da | ||
|
|
72486b448c | ||
|
|
7dea1b7870 | ||
|
|
4de2c496c9 | ||
|
|
9e1393a392 | ||
|
|
0901690ed6 | ||
|
|
95303648cc | ||
|
|
1dbb08c045 | ||
|
|
00a7d9a180 | ||
|
|
6c549dc086 | ||
|
|
dc368e326a | ||
|
|
e42188a627 | ||
|
|
7a6a337eff | ||
|
|
3907344884 |
@@ -12,7 +12,7 @@ module.exports = {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"]
|
||||
},
|
||||
ignorePatterns: ["scripts/**/*", "plugin-runtime/**/*", "src-tauri/**/*", "plugins/**/*"],
|
||||
ignorePatterns: ["src-tauri/**/*", "plugins/**/*"],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
|
||||
72
.github/workflows/artifacts.yml
vendored
Normal file
72
.github/workflows/artifacts.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Generate Artifacts
|
||||
on:
|
||||
push:
|
||||
tags: [ v* ]
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
name: Build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
- os: ubuntu-20.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- name: Cache Rust
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
./src-tauri/target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
# Pin dev version to get non-default targets
|
||||
# https://github.com/tauri-apps/tauri-action/issues/356
|
||||
- uses: tauri-apps/tauri-action@dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
with:
|
||||
tagName: 'v__VERSION__'
|
||||
releaseName: 'Release __VERSION__'
|
||||
releaseBody: 'https://yaak.app/changelog/__VERSION__'
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: '--target ${{ matrix.target }}'
|
||||
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
|
||||
120
.github/workflows/release.yml
vendored
120
.github/workflows/release.yml
vendored
@@ -1,120 +0,0 @@
|
||||
name: Generate Artifacts
|
||||
on:
|
||||
push:
|
||||
tags: [ v* ]
|
||||
|
||||
env:
|
||||
YAAK_PLUGINS_DIR: checkout/plugins
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
name: Build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- 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.
|
||||
args: '--target x86_64-apple-darwin'
|
||||
yaak_arch: 'x64'
|
||||
- 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 }}
|
||||
steps:
|
||||
- name: Checkout yaakapp/app
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- 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-
|
||||
|
||||
- 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 Node dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Install plugin-runtime Node dependencies
|
||||
working-directory: plugin-runtime
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Install Protoc for plugin-runtime
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install yaak CLI
|
||||
run: go install github.com/yaakapp/yaakcli@latest
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Checkout yaakapp/plugins
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: yaakapp/plugins
|
||||
path: ${{ env.YAAK_PLUGINS_DIR }}
|
||||
|
||||
- name: Set version
|
||||
run: npm run replace-version
|
||||
env:
|
||||
YAAK_VERSION: ${{ github.ref_name }}
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
YAAK_PLUGINS_DIR: ${{ env.YAAK_PLUGINS_DIR }}
|
||||
YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
with:
|
||||
tagName: 'v__VERSION__'
|
||||
releaseName: 'Release __VERSION__'
|
||||
releaseBody: 'https://yaak.app/changelog/__VERSION__'
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -26,8 +26,3 @@ dist-ssr
|
||||
|
||||
*.sqlite
|
||||
*.sqlite-*
|
||||
|
||||
.cargo
|
||||
|
||||
.tmp
|
||||
tmp
|
||||
|
||||
20
.run/Build Desktop.run.xml
Normal file
20
.run/Build Desktop.run.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build Desktop" type="ShConfigurationType">
|
||||
<option name="SCRIPT_TEXT" value="npm run tauri build -- --target universal-apple-darwin" />
|
||||
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||
<option name="SCRIPT_PATH" value="" />
|
||||
<option name="SCRIPT_OPTIONS" value="" />
|
||||
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||
<option name="INTERPRETER_PATH" value="/bin/zsh" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||
<option name="EXECUTE_SCRIPT_FILE" value="false" />
|
||||
<envs>
|
||||
<env name="TAURI_KEY_PASSWORD" value="fishhook-upstream-wash-assured" />
|
||||
<env name="TAURI_PRIVATE_KEY" value="dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5OGxWaytTa3dIa2xXVUltQzRGUXIzd2lYQ2NpV0ZhQURSbWJWZ1NrK0tnY0FBQkFBQUFBQUFBQUFBQUlBQUFBQUV2M1VKdVRyVHpHSzhQdGc2ZVFtOVNsMU5tNEVSN280cFNrbXhncW9tdjNXaFJZUTJqUzQ5Q01zWTJWRVhaY1pGNHNjR1NFR3JmcWFRN09NdWdGMXpZVXhzejR4V3lDV1JpZHlnbW5LNS9vMFFtRlZjbUl4YjZSNzhlMmk3ait5SExYcG5QZUkxOFE9Cg==" />
|
||||
</envs>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
1
.tauriignore
Normal file
1
.tauriignore
Normal file
@@ -0,0 +1 @@
|
||||
plugins
|
||||
Binary file not shown.
22
index.html
22
index.html
@@ -1,27 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<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: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="text-base">
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="cm-portal" class="cm-portal"></div>
|
||||
<div id="react-portal"></div>
|
||||
|
||||
4257
package-lock.json
generated
4257
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
@@ -4,27 +4,29 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"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",
|
||||
"start": "npm run build:plugins && npm run tauri-dev",
|
||||
"tauri-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
|
||||
"tauri-build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"dev:js": "vite 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": "run-p build:*",
|
||||
"build:js": "vite build",
|
||||
"build:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||
"build:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||
"build:vendor-node": "node scripts/vendor-node.cjs",
|
||||
"build:plugin-runtime": "npm run --prefix plugin-runtime build",
|
||||
"prepare": "husky install",
|
||||
"replace-version": "node scripts/replace-version.cjs"
|
||||
"build:frontend": "vite build",
|
||||
"build:plugins": "run-p build:plugin:*",
|
||||
"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: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": {
|
||||
"@codemirror/commands": "^6.2.1",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-javascript": "^6.1.4",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-xml": "^6.0.2",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
@@ -34,25 +36,21 @@
|
||||
"@lezer/lr": "^1.3.3",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@tailwindcss/container-queries": "^0.1.0",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"@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",
|
||||
"@tanstack/query-sync-storage-persister": "^4.27.1",
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"@tanstack/react-query-devtools": "^4.28.0",
|
||||
"@tanstack/react-query-persist-client": "^4.28.0",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"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",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"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",
|
||||
@@ -60,18 +58,16 @@
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-pdf": "^9.0.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",
|
||||
"tauri-plugin-log-api": "github:tauri-apps/tauri-plugin-log#v1",
|
||||
"uuid": "^9.0.0",
|
||||
"xml-formatter": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||
"@tanstack/react-query-devtools": "^5.45.1",
|
||||
"@tauri-apps/cli": ">=2.0.0-beta.0",
|
||||
"@tauri-apps/cli": "^1.5.10",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/parse-color": "^1.0.1",
|
||||
@@ -90,7 +86,6 @@
|
||||
"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",
|
||||
"postcss": "^8.4.21",
|
||||
@@ -98,14 +93,11 @@
|
||||
"prettier": "^2.8.4",
|
||||
"react-devtools": "^4.27.2",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"decompress": "^4.2.1",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"rimraf": "^5.0.7",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.1",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-plugin-top-level-await": "^1.4.1"
|
||||
"vite-plugin-top-level-await": "^1.4.1",
|
||||
"vitest": "^1.3.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --cache --fix",
|
||||
|
||||
3
plugin-runtime/.gitignore
vendored
3
plugin-runtime/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
build
|
||||
node_modules
|
||||
*.blob
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts",
|
||||
"exec": "node -r ts-node/register ./src/index.ts"
|
||||
}
|
||||
3004
plugin-runtime/package-lock.json
generated
3004
plugin-runtime/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "@yaak/plugin-runtime",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "run-p build:*",
|
||||
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=build/index.cjs",
|
||||
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=build/index.worker.cjs",
|
||||
"build:proto": "grpc_tools_node_protoc --ts_proto_out=src/gen --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false --proto_path=../proto ../proto/plugins/*.proto"
|
||||
},
|
||||
"dependencies": {
|
||||
"long": "^5.2.3",
|
||||
"nice-grpc": "^2.1.9",
|
||||
"protobufjs": "^7.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grpc-tools": "^1.12.4",
|
||||
"nodemon": "^3.1.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-proto": "^1.180.0",
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { PluginInfo } from './plugins';
|
||||
|
||||
export interface ParentToWorkerEvent<T = any> {
|
||||
callbackId: string;
|
||||
name: string;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export type WorkerToParentSuccessEvent<T> = {
|
||||
callbackId: string;
|
||||
payload: T;
|
||||
};
|
||||
|
||||
export type WorkerToParentErrorEvent = {
|
||||
callbackId: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export type WorkerToParentEvent<T = any> = WorkerToParentErrorEvent | WorkerToParentSuccessEvent<T>;
|
||||
|
||||
export class PluginHandle {
|
||||
readonly pluginDir: string;
|
||||
readonly #worker: Worker;
|
||||
|
||||
constructor(pluginDir: string) {
|
||||
this.pluginDir = pluginDir;
|
||||
|
||||
const workerPath = path.join(__dirname, 'index.worker.cjs');
|
||||
this.#worker = new Worker(workerPath, {
|
||||
workerData: {
|
||||
pluginDir: this.pluginDir,
|
||||
},
|
||||
});
|
||||
|
||||
this.#worker.on('error', this.#handleError.bind(this));
|
||||
this.#worker.on('exit', this.#handleExit.bind(this));
|
||||
}
|
||||
|
||||
async getInfo(): Promise<PluginInfo> {
|
||||
return this.#callPlugin('info', null);
|
||||
}
|
||||
|
||||
async runResponseFilter({ filter, body }: { filter: string; body: string }): Promise<string> {
|
||||
return this.#callPlugin('run-filter', { filter, body });
|
||||
}
|
||||
|
||||
async runExport(request: any): Promise<string> {
|
||||
return this.#callPlugin('run-export', request);
|
||||
}
|
||||
|
||||
async runImport(data: string): Promise<string> {
|
||||
const result = await this.#callPlugin('run-import', data);
|
||||
|
||||
// Plugin returns object, but we convert to string
|
||||
return JSON.stringify(result, null, 2);
|
||||
}
|
||||
|
||||
#callPlugin<P, R>(name: string, payload: P): Promise<R> {
|
||||
const callbackId = `cb_${randomUUID().replaceAll('-', '')}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
const cb = (e: WorkerToParentEvent<R>) => {
|
||||
if (e.callbackId !== callbackId) return;
|
||||
|
||||
if ('error' in e) {
|
||||
reject(e.error);
|
||||
} else {
|
||||
resolve(e.payload as R);
|
||||
}
|
||||
|
||||
this.#worker.removeListener('message', cb);
|
||||
};
|
||||
|
||||
this.#worker.addListener('message', cb);
|
||||
this.#worker.postMessage({ callbackId, name, payload });
|
||||
});
|
||||
}
|
||||
|
||||
async #handleError(err: Error) {
|
||||
console.error('Plugin errored', this.pluginDir, err);
|
||||
}
|
||||
|
||||
async #handleExit(code: number) {
|
||||
if (code === 0) {
|
||||
console.log('Plugin exited successfully', this.pluginDir);
|
||||
} else {
|
||||
console.log('Plugin exited with error', code, this.pluginDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { PluginHandle } from './PluginHandle';
|
||||
import { loadPlugins, PluginInfo } from './plugins';
|
||||
|
||||
export class PluginManager {
|
||||
#handles: PluginHandle[] | null = null;
|
||||
static #instance: PluginManager | null = null;
|
||||
|
||||
public static instance(): PluginManager {
|
||||
if (PluginManager.#instance == null) {
|
||||
PluginManager.#instance = new PluginManager();
|
||||
PluginManager.#instance.plugins(); // Trigger workers to boot, as it takes a few seconds
|
||||
}
|
||||
return PluginManager.#instance;
|
||||
}
|
||||
|
||||
async plugins(): Promise<PluginHandle[]> {
|
||||
this.#handles = this.#handles ?? loadPlugins();
|
||||
return this.#handles;
|
||||
}
|
||||
|
||||
async #pluginsWithInfo(): Promise<{ plugin: PluginHandle; info: PluginInfo }[]> {
|
||||
const plugins = await this.plugins();
|
||||
return Promise.all(plugins.map(async (plugin) => ({ plugin, info: await plugin.getInfo() })));
|
||||
}
|
||||
|
||||
async pluginsWith(capability: PluginInfo['capabilities'][0]): Promise<PluginHandle[]> {
|
||||
return (await this.#pluginsWithInfo())
|
||||
.filter((v) => v.info.capabilities.includes(capability))
|
||||
.map((v) => v.plugin);
|
||||
}
|
||||
|
||||
async plugin(name: string): Promise<PluginHandle | null> {
|
||||
return (await this.#pluginsWithInfo()).find((v) => v.info.name === name)?.plugin ?? null;
|
||||
}
|
||||
|
||||
async pluginOrThrow(name: string): Promise<PluginHandle> {
|
||||
const plugin = await this.plugin(name);
|
||||
if (plugin == null) {
|
||||
throw new Error(`Failed to find plugin by ${name}`);
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v1.180.0
|
||||
// protoc v3.19.1
|
||||
// source: plugins/runtime.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { type CallContext, type CallOptions } from "nice-grpc-common";
|
||||
import * as _m0 from "protobufjs/minimal";
|
||||
|
||||
export const protobufPackage = "yaak.plugins.runtime";
|
||||
|
||||
export interface PluginInfo {
|
||||
plugin: string;
|
||||
}
|
||||
|
||||
export interface HookResponse {
|
||||
info: PluginInfo | undefined;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface HookImportRequest {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface HookResponseFilterRequest {
|
||||
filter: string;
|
||||
body: string;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface HookExportRequest {
|
||||
request: string;
|
||||
}
|
||||
|
||||
function createBasePluginInfo(): PluginInfo {
|
||||
return { plugin: "" };
|
||||
}
|
||||
|
||||
export const PluginInfo = {
|
||||
encode(message: PluginInfo, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.plugin !== "") {
|
||||
writer.uint32(10).string(message.plugin);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): PluginInfo {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePluginInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.plugin = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): PluginInfo {
|
||||
return { plugin: isSet(object.plugin) ? globalThis.String(object.plugin) : "" };
|
||||
},
|
||||
|
||||
toJSON(message: PluginInfo): unknown {
|
||||
const obj: any = {};
|
||||
if (message.plugin !== "") {
|
||||
obj.plugin = message.plugin;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<PluginInfo>): PluginInfo {
|
||||
return PluginInfo.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<PluginInfo>): PluginInfo {
|
||||
const message = createBasePluginInfo();
|
||||
message.plugin = object.plugin ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseHookResponse(): HookResponse {
|
||||
return { info: undefined, data: "" };
|
||||
}
|
||||
|
||||
export const HookResponse = {
|
||||
encode(message: HookResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.info !== undefined) {
|
||||
PluginInfo.encode(message.info, writer.uint32(10).fork()).ldelim();
|
||||
}
|
||||
if (message.data !== "") {
|
||||
writer.uint32(18).string(message.data);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): HookResponse {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseHookResponse();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.info = PluginInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.data = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): HookResponse {
|
||||
return {
|
||||
info: isSet(object.info) ? PluginInfo.fromJSON(object.info) : undefined,
|
||||
data: isSet(object.data) ? globalThis.String(object.data) : "",
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: HookResponse): unknown {
|
||||
const obj: any = {};
|
||||
if (message.info !== undefined) {
|
||||
obj.info = PluginInfo.toJSON(message.info);
|
||||
}
|
||||
if (message.data !== "") {
|
||||
obj.data = message.data;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<HookResponse>): HookResponse {
|
||||
return HookResponse.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<HookResponse>): HookResponse {
|
||||
const message = createBaseHookResponse();
|
||||
message.info = (object.info !== undefined && object.info !== null)
|
||||
? PluginInfo.fromPartial(object.info)
|
||||
: undefined;
|
||||
message.data = object.data ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseHookImportRequest(): HookImportRequest {
|
||||
return { data: "" };
|
||||
}
|
||||
|
||||
export const HookImportRequest = {
|
||||
encode(message: HookImportRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.data !== "") {
|
||||
writer.uint32(10).string(message.data);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): HookImportRequest {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseHookImportRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.data = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): HookImportRequest {
|
||||
return { data: isSet(object.data) ? globalThis.String(object.data) : "" };
|
||||
},
|
||||
|
||||
toJSON(message: HookImportRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.data !== "") {
|
||||
obj.data = message.data;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<HookImportRequest>): HookImportRequest {
|
||||
return HookImportRequest.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<HookImportRequest>): HookImportRequest {
|
||||
const message = createBaseHookImportRequest();
|
||||
message.data = object.data ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseHookResponseFilterRequest(): HookResponseFilterRequest {
|
||||
return { filter: "", body: "", contentType: "" };
|
||||
}
|
||||
|
||||
export const HookResponseFilterRequest = {
|
||||
encode(message: HookResponseFilterRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.filter !== "") {
|
||||
writer.uint32(10).string(message.filter);
|
||||
}
|
||||
if (message.body !== "") {
|
||||
writer.uint32(18).string(message.body);
|
||||
}
|
||||
if (message.contentType !== "") {
|
||||
writer.uint32(26).string(message.contentType);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): HookResponseFilterRequest {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseHookResponseFilterRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.filter = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.body = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentType = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): HookResponseFilterRequest {
|
||||
return {
|
||||
filter: isSet(object.filter) ? globalThis.String(object.filter) : "",
|
||||
body: isSet(object.body) ? globalThis.String(object.body) : "",
|
||||
contentType: isSet(object.contentType) ? globalThis.String(object.contentType) : "",
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: HookResponseFilterRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.filter !== "") {
|
||||
obj.filter = message.filter;
|
||||
}
|
||||
if (message.body !== "") {
|
||||
obj.body = message.body;
|
||||
}
|
||||
if (message.contentType !== "") {
|
||||
obj.contentType = message.contentType;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<HookResponseFilterRequest>): HookResponseFilterRequest {
|
||||
return HookResponseFilterRequest.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<HookResponseFilterRequest>): HookResponseFilterRequest {
|
||||
const message = createBaseHookResponseFilterRequest();
|
||||
message.filter = object.filter ?? "";
|
||||
message.body = object.body ?? "";
|
||||
message.contentType = object.contentType ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseHookExportRequest(): HookExportRequest {
|
||||
return { request: "" };
|
||||
}
|
||||
|
||||
export const HookExportRequest = {
|
||||
encode(message: HookExportRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.request !== "") {
|
||||
writer.uint32(10).string(message.request);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): HookExportRequest {
|
||||
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseHookExportRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.request = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): HookExportRequest {
|
||||
return { request: isSet(object.request) ? globalThis.String(object.request) : "" };
|
||||
},
|
||||
|
||||
toJSON(message: HookExportRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.request !== "") {
|
||||
obj.request = message.request;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create(base?: DeepPartial<HookExportRequest>): HookExportRequest {
|
||||
return HookExportRequest.fromPartial(base ?? {});
|
||||
},
|
||||
fromPartial(object: DeepPartial<HookExportRequest>): HookExportRequest {
|
||||
const message = createBaseHookExportRequest();
|
||||
message.request = object.request ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export type PluginRuntimeDefinition = typeof PluginRuntimeDefinition;
|
||||
export const PluginRuntimeDefinition = {
|
||||
name: "PluginRuntime",
|
||||
fullName: "yaak.plugins.runtime.PluginRuntime",
|
||||
methods: {
|
||||
hookImport: {
|
||||
name: "hookImport",
|
||||
requestType: HookImportRequest,
|
||||
requestStream: false,
|
||||
responseType: HookResponse,
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
hookResponseFilter: {
|
||||
name: "hookResponseFilter",
|
||||
requestType: HookResponseFilterRequest,
|
||||
requestStream: false,
|
||||
responseType: HookResponse,
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
hookExport: {
|
||||
name: "hookExport",
|
||||
requestType: HookExportRequest,
|
||||
requestStream: false,
|
||||
responseType: HookResponse,
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface PluginRuntimeServiceImplementation<CallContextExt = {}> {
|
||||
hookImport(request: HookImportRequest, context: CallContext & CallContextExt): Promise<DeepPartial<HookResponse>>;
|
||||
hookResponseFilter(
|
||||
request: HookResponseFilterRequest,
|
||||
context: CallContext & CallContextExt,
|
||||
): Promise<DeepPartial<HookResponse>>;
|
||||
hookExport(request: HookExportRequest, context: CallContext & CallContextExt): Promise<DeepPartial<HookResponse>>;
|
||||
}
|
||||
|
||||
export interface PluginRuntimeClient<CallOptionsExt = {}> {
|
||||
hookImport(request: DeepPartial<HookImportRequest>, options?: CallOptions & CallOptionsExt): Promise<HookResponse>;
|
||||
hookResponseFilter(
|
||||
request: DeepPartial<HookResponseFilterRequest>,
|
||||
options?: CallOptions & CallOptionsExt,
|
||||
): Promise<HookResponse>;
|
||||
hookExport(request: DeepPartial<HookExportRequest>, options?: CallOptions & CallOptionsExt): Promise<HookResponse>;
|
||||
}
|
||||
|
||||
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
|
||||
|
||||
export type DeepPartial<T> = T extends Builtin ? T
|
||||
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>;
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { isAbortError } from 'abort-controller-x';
|
||||
import { createServer, ServerError, ServerMiddlewareCall, Status } from 'nice-grpc';
|
||||
import { CallContext } from 'nice-grpc-common';
|
||||
import * as fs from 'node:fs';
|
||||
import {
|
||||
DeepPartial,
|
||||
HookExportRequest,
|
||||
HookImportRequest,
|
||||
HookResponse,
|
||||
HookResponseFilterRequest,
|
||||
PluginRuntimeDefinition,
|
||||
PluginRuntimeServiceImplementation,
|
||||
} from './gen/plugins/runtime';
|
||||
import { PluginManager } from './PluginManager';
|
||||
|
||||
class PluginRuntimeService implements PluginRuntimeServiceImplementation {
|
||||
#manager: PluginManager;
|
||||
|
||||
constructor() {
|
||||
this.#manager = PluginManager.instance();
|
||||
}
|
||||
|
||||
async hookExport(request: HookExportRequest): Promise<DeepPartial<HookResponse>> {
|
||||
const plugin = await this.#manager.pluginOrThrow('exporter-curl');
|
||||
const data = await plugin.runExport(JSON.parse(request.request));
|
||||
const info = { plugin: (await plugin.getInfo()).name };
|
||||
return { info, data };
|
||||
}
|
||||
|
||||
async hookImport(request: HookImportRequest): Promise<DeepPartial<HookResponse>> {
|
||||
const plugins = await this.#manager.pluginsWith('import');
|
||||
for (const p of plugins) {
|
||||
const data = await p.runImport(request.data);
|
||||
if (data != null && data !== 'null') {
|
||||
const info = { plugin: (await p.getInfo()).name };
|
||||
return { info, data };
|
||||
}
|
||||
}
|
||||
|
||||
throw new ServerError(Status.UNKNOWN, 'No importers found for data');
|
||||
}
|
||||
|
||||
async hookResponseFilter(request: HookResponseFilterRequest): Promise<DeepPartial<HookResponse>> {
|
||||
const pluginName = request.contentType.includes('json') ? 'filter-jsonpath' : 'filter-xpath';
|
||||
const plugin = await this.#manager.pluginOrThrow(pluginName);
|
||||
const data = await plugin.runResponseFilter(request);
|
||||
const info = { plugin: (await plugin.getInfo()).name };
|
||||
return { info, data };
|
||||
}
|
||||
}
|
||||
|
||||
let server = createServer();
|
||||
|
||||
async function* errorHandlingMiddleware<Request, Response>(
|
||||
call: ServerMiddlewareCall<Request, Response>,
|
||||
context: CallContext,
|
||||
) {
|
||||
try {
|
||||
return yield* call.next(call.request, context);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof ServerError || isAbortError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let details = String(error);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// @ts-ignore
|
||||
details += `: ${error.stack}`;
|
||||
}
|
||||
|
||||
throw new ServerError(Status.UNKNOWN, details);
|
||||
}
|
||||
}
|
||||
|
||||
server = server.use(errorHandlingMiddleware);
|
||||
server.add(PluginRuntimeDefinition, new PluginRuntimeService());
|
||||
|
||||
// Start on random port if YAAK_GRPC_PORT_FILE_PATH is set, or :4000
|
||||
const addr = process.env.YAAK_GRPC_PORT_FILE_PATH ? 'localhost:0' : 'localhost:4000';
|
||||
server.listen(addr).then((port) => {
|
||||
console.log('gRPC server listening on', `http://localhost:${port}`);
|
||||
if (process.env.YAAK_GRPC_PORT_FILE_PATH) {
|
||||
console.log('Wrote port file to', process.env.YAAK_GRPC_PORT_FILE_PATH);
|
||||
fs.writeFileSync(process.env.YAAK_GRPC_PORT_FILE_PATH, JSON.stringify({ port }, null, 2));
|
||||
}
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { parentPort, workerData } from 'node:worker_threads';
|
||||
import { ParentToWorkerEvent } from './PluginHandle';
|
||||
import { PluginInfo } from './plugins';
|
||||
|
||||
new Promise<void>(async (resolve, reject) => {
|
||||
const { pluginDir } = workerData;
|
||||
const pathMod = path.join(pluginDir, 'build/index.js');
|
||||
const pathPkg = path.join(pluginDir, 'package.json');
|
||||
|
||||
let pkg: { [x: string]: any };
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(pathPkg, 'utf8'));
|
||||
} catch (err) {
|
||||
// TODO: Do something better here
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const mod = (await import(`file://${pathMod}`)).default ?? {};
|
||||
|
||||
const info: PluginInfo = {
|
||||
capabilities: [],
|
||||
name: pkg['name'] ?? 'n/a',
|
||||
dir: pluginDir,
|
||||
};
|
||||
|
||||
if (typeof mod['pluginHookImport'] === 'function') {
|
||||
info.capabilities.push('import');
|
||||
}
|
||||
|
||||
if (typeof mod['pluginHookExport'] === 'function') {
|
||||
info.capabilities.push('export');
|
||||
}
|
||||
|
||||
if (typeof mod['pluginHookResponseFilter'] === 'function') {
|
||||
info.capabilities.push('filter');
|
||||
}
|
||||
|
||||
console.log('Loaded plugin', info.name, info.capabilities, info.dir);
|
||||
|
||||
function reply<T>(originalMsg: ParentToWorkerEvent, payload: T) {
|
||||
parentPort!.postMessage({ payload, callbackId: originalMsg.callbackId });
|
||||
}
|
||||
|
||||
function replyErr(originalMsg: ParentToWorkerEvent, error: unknown) {
|
||||
parentPort!.postMessage({
|
||||
error: String(error),
|
||||
callbackId: originalMsg.callbackId,
|
||||
});
|
||||
}
|
||||
|
||||
parentPort!.on('message', async (msg: ParentToWorkerEvent) => {
|
||||
try {
|
||||
const ctx = { todo: 'implement me' };
|
||||
if (msg.name === 'run-import') {
|
||||
reply(msg, await mod.pluginHookImport(ctx, msg.payload));
|
||||
} else if (msg.name === 'run-filter') {
|
||||
reply(msg, await mod.pluginHookResponseFilter(ctx, msg.payload));
|
||||
} else if (msg.name === 'run-export') {
|
||||
reply(msg, await mod.pluginHookExport(ctx, msg.payload));
|
||||
} else if (msg.name === 'info') {
|
||||
reply(msg, info);
|
||||
} else {
|
||||
console.log('Unknown message', msg);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
replyErr(msg, err);
|
||||
}
|
||||
});
|
||||
|
||||
resolve();
|
||||
}).catch((err) => {
|
||||
console.log('failed to boot plugin', err);
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import * as fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { PluginHandle } from './PluginHandle';
|
||||
|
||||
export interface PluginInfo {
|
||||
name: string;
|
||||
dir: string;
|
||||
capabilities: ('import' | 'export' | 'filter')[];
|
||||
}
|
||||
|
||||
export function loadPlugins(): PluginHandle[] {
|
||||
const pluginsDir = process.env.YAAK_PLUGINS_DIR;
|
||||
if (!pluginsDir) throw new Error('YAAK_PLUGINS_DIR is not set');
|
||||
console.log('Loading plugins from', pluginsDir);
|
||||
|
||||
const pluginDirs = fs.readdirSync(pluginsDir).map((p) => path.join(pluginsDir, p));
|
||||
return pluginDirs.map((pluginDir) => new PluginHandle(pluginDir));
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2021",
|
||||
"lib": ["es2021"],
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
"src/types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
plugins/filter-jsonpath/package.json
Normal file
7
plugins/filter-jsonpath/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "filter-jsonpath",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"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) };
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
plugins/filter-xpath/package.json
Normal file
8
plugins/filter-xpath/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"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 };
|
||||
}
|
||||
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'),
|
||||
},
|
||||
});
|
||||
12
plugins/importer-insomnia/package-lock.json
generated
Normal file
12
plugins/importer-insomnia/package-lock.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "importer-insomnia",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "importer-insomnia",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
plugins/importer-insomnia/package.json
Normal file
4
plugins/importer-insomnia/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "importer-insomnia",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
23
plugins/importer-insomnia/src/helpers/types.js
Normal file
23
plugins/importer-insomnia/src/helpers/types.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export function isWorkspace(obj) {
|
||||
return isJSObject(obj) && obj._type === 'workspace';
|
||||
}
|
||||
|
||||
export function isRequestGroup(obj) {
|
||||
return isJSObject(obj) && obj._type === 'request_group';
|
||||
}
|
||||
|
||||
export function isRequest(obj) {
|
||||
return isJSObject(obj) && obj._type === 'request';
|
||||
}
|
||||
|
||||
export function isEnvironment(obj) {
|
||||
return isJSObject(obj) && obj._type === 'environment';
|
||||
}
|
||||
|
||||
export function isJSObject(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
||||
|
||||
export function isJSString(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object String]';
|
||||
}
|
||||
18
plugins/importer-insomnia/src/helpers/variables.js
Normal file
18
plugins/importer-insomnia/src/helpers/variables.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { isJSString } from './types.js';
|
||||
|
||||
export function parseVariables(data) {
|
||||
return Object.entries(data).map(([name, value]) => ({
|
||||
enabled: true,
|
||||
name,
|
||||
value: `${value}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Insomnia syntax to Yaak syntax
|
||||
* @param {string} variable - Text to convert
|
||||
*/
|
||||
export function convertSyntax(variable) {
|
||||
if (!isJSString(variable)) return variable;
|
||||
return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}');
|
||||
}
|
||||
21
plugins/importer-insomnia/src/importers/environment.js
Normal file
21
plugins/importer-insomnia/src/importers/environment.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Import an Insomnia environment object.
|
||||
* @param {Object} e - The environment object to import.
|
||||
* @param workspaceId - Workspace to import into.
|
||||
*/
|
||||
export function importEnvironment(e, workspaceId) {
|
||||
console.log('IMPORTING Environment', e._id, e.name, JSON.stringify(e, null, 2));
|
||||
return {
|
||||
id: e._id,
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||
workspaceId,
|
||||
model: 'environment',
|
||||
name: e.name,
|
||||
variables: Object.entries(e.data).map(([name, value]) => ({
|
||||
enabled: true,
|
||||
name,
|
||||
value: `${value}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
17
plugins/importer-insomnia/src/importers/folder.js
Normal file
17
plugins/importer-insomnia/src/importers/folder.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Import an Insomnia folder object.
|
||||
* @param {Object} f - The environment object to import.
|
||||
* @param workspaceId - Workspace to import into.
|
||||
*/
|
||||
export function importFolder(f, workspaceId) {
|
||||
console.log('IMPORTING FOLDER', f._id, f.name, JSON.stringify(f, null, 2));
|
||||
return {
|
||||
id: f._id,
|
||||
createdAt: new Date(f.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||
updatedAt: new Date(f.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||
folderId: f.parentId === workspaceId ? null : f.parentId,
|
||||
workspaceId,
|
||||
model: 'folder',
|
||||
name: f.name,
|
||||
};
|
||||
}
|
||||
60
plugins/importer-insomnia/src/importers/request.js
Normal file
60
plugins/importer-insomnia/src/importers/request.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { convertSyntax } from '../helpers/variables.js';
|
||||
|
||||
/**
|
||||
* Import an Insomnia request object.
|
||||
* @param {Object} r - The request object to import.
|
||||
* @param workspaceId - The workspace ID to use for the request.
|
||||
* @param {number} sortPriority - The sort priority to use for the request.
|
||||
*/
|
||||
export function importRequest(r, workspaceId, sortPriority = 0) {
|
||||
console.log('IMPORTING REQUEST', r._id, r.name, JSON.stringify(r, null, 2));
|
||||
|
||||
let bodyType = null;
|
||||
let body = null;
|
||||
if (r.body?.mimeType === 'application/graphql') {
|
||||
bodyType = 'graphql';
|
||||
body = convertSyntax(r.body.text);
|
||||
} else if (r.body?.mimeType === 'application/json') {
|
||||
bodyType = 'application/json';
|
||||
body = convertSyntax(r.body.text);
|
||||
}
|
||||
|
||||
let authenticationType = null;
|
||||
let authentication = {};
|
||||
if (r.authentication.type === 'bearer') {
|
||||
authenticationType = 'bearer';
|
||||
authentication = {
|
||||
token: convertSyntax(r.authentication.token),
|
||||
};
|
||||
} else if (r.authentication.type === 'basic') {
|
||||
authenticationType = 'basic';
|
||||
authentication = {
|
||||
username: convertSyntax(r.authentication.username),
|
||||
password: convertSyntax(r.authentication.password),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: r._id,
|
||||
createdAt: new Date(r.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||
updatedAt: new Date(r.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||
workspaceId,
|
||||
folderId: r.parentId === workspaceId ? null : r.parentId,
|
||||
model: 'http_request',
|
||||
sortPriority,
|
||||
name: r.name,
|
||||
url: convertSyntax(r.url),
|
||||
body,
|
||||
bodyType,
|
||||
authentication,
|
||||
authenticationType,
|
||||
method: r.method,
|
||||
headers: (r.headers ?? [])
|
||||
.map(({ name, value, disabled }) => ({
|
||||
enabled: !disabled,
|
||||
name,
|
||||
value,
|
||||
}))
|
||||
.filter(({ name, value }) => name !== '' || value !== ''),
|
||||
};
|
||||
}
|
||||
76
plugins/importer-insomnia/src/index.js
Normal file
76
plugins/importer-insomnia/src/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { importEnvironment } from './importers/environment.js';
|
||||
import { importRequest } from './importers/request.js';
|
||||
import {
|
||||
isEnvironment,
|
||||
isJSObject,
|
||||
isRequest,
|
||||
isRequestGroup,
|
||||
isWorkspace,
|
||||
} from './helpers/types.js';
|
||||
import { parseVariables } from './helpers/variables.js';
|
||||
import { importFolder } from './importers/folder.js';
|
||||
|
||||
export function pluginHookImport(contents) {
|
||||
console.log('RUNNING INSOMNIA');
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(contents);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isJSObject(parsed)) return;
|
||||
if (!Array.isArray(parsed.resources)) return;
|
||||
|
||||
const resources = {
|
||||
workspaces: [],
|
||||
requests: [],
|
||||
environments: [],
|
||||
folders: [],
|
||||
};
|
||||
|
||||
// Import workspaces
|
||||
const workspacesToImport = parsed.resources.filter(isWorkspace);
|
||||
for (const workspaceToImport of workspacesToImport) {
|
||||
const baseEnvironment = parsed.resources.find(
|
||||
(r) => isEnvironment(r) && r.parentId === workspaceToImport._id,
|
||||
);
|
||||
resources.workspaces.push({
|
||||
id: workspaceToImport._id,
|
||||
createdAt: new Date(workspacesToImport.created ?? Date.now()).toISOString().replace('Z', ''),
|
||||
updatedAt: new Date(workspacesToImport.updated ?? Date.now()).toISOString().replace('Z', ''),
|
||||
model: 'workspace',
|
||||
name: workspaceToImport.name,
|
||||
variables: baseEnvironment ? parseVariables(baseEnvironment.data) : [],
|
||||
});
|
||||
const environmentsToImport = parsed.resources.filter(
|
||||
(r) => isEnvironment(r) && r.parentId === baseEnvironment?._id,
|
||||
);
|
||||
resources.environments.push(
|
||||
...environmentsToImport.map((r) => importEnvironment(r, workspaceToImport._id)),
|
||||
);
|
||||
|
||||
const nextFolder = (parentId) => {
|
||||
const children = parsed.resources.filter((r) => r.parentId === parentId);
|
||||
let sortPriority = 0;
|
||||
for (const child of children) {
|
||||
if (isRequestGroup(child)) {
|
||||
resources.folders.push(importFolder(child, workspaceToImport._id));
|
||||
nextFolder(child._id);
|
||||
} else if (isRequest(child)) {
|
||||
resources.requests.push(importRequest(child, workspaceToImport._id, sortPriority++));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Import folders
|
||||
nextFolder(workspaceToImport._id);
|
||||
}
|
||||
|
||||
// Filter out any `null` values
|
||||
resources.requests = resources.requests.filter(Boolean);
|
||||
resources.environments = resources.environments.filter(Boolean);
|
||||
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||
|
||||
return { resources };
|
||||
}
|
||||
13
plugins/importer-insomnia/vite.config.js
Normal file
13
plugins/importer-insomnia/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/importer-insomnia'),
|
||||
},
|
||||
});
|
||||
12
plugins/importer-postman/package-lock.json
generated
Normal file
12
plugins/importer-postman/package-lock.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "importer-postman",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "importer-postman",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
plugins/importer-postman/package.json
Normal file
4
plugins/importer-postman/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "importer-postman",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
223
plugins/importer-postman/src/index.ts
Normal file
223
plugins/importer-postman/src/index.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { Environment, Folder, HttpRequest, Workspace } from '../../../src-web/lib/models';
|
||||
|
||||
const POSTMAN_2_1_0_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
|
||||
const POSTMAN_2_0_0_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json';
|
||||
const VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA];
|
||||
|
||||
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
|
||||
interface ExportResources {
|
||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
requests: AtLeast<HttpRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
}
|
||||
|
||||
export function pluginHookImport(contents: string): { resources: ExportResources } | undefined {
|
||||
const root = parseJSONToRecord(contents);
|
||||
if (root == null) return;
|
||||
|
||||
const info = toRecord(root.info);
|
||||
const isValidSchema = VALID_SCHEMAS.includes(info.schema);
|
||||
if (!isValidSchema || !Array.isArray(root.item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exportResources: ExportResources = {
|
||||
workspaces: [],
|
||||
environments: [],
|
||||
requests: [],
|
||||
folders: [],
|
||||
};
|
||||
|
||||
const workspace: ExportResources['workspaces'][0] = {
|
||||
model: 'workspace',
|
||||
id: generateId('wk'),
|
||||
name: info.name || 'Postman Import',
|
||||
description: info.description || '',
|
||||
};
|
||||
exportResources.workspaces.push(workspace);
|
||||
|
||||
const importItem = (v: Record<string, any>, folderId: string | null = null) => {
|
||||
if (typeof v.name === 'string' && Array.isArray(v.item)) {
|
||||
const folder: ExportResources['folders'][0] = {
|
||||
model: 'folder',
|
||||
workspaceId: workspace.id,
|
||||
id: generateId('fl'),
|
||||
name: v.name,
|
||||
folderId,
|
||||
};
|
||||
exportResources.folders.push(folder);
|
||||
for (const child of v.item) {
|
||||
importItem(child, folder.id);
|
||||
}
|
||||
} else if (typeof v.name === 'string' && 'request' in v) {
|
||||
const r = toRecord(v.request);
|
||||
const bodyPatch = importBody(r.body);
|
||||
const authPatch = importAuth(r.auth);
|
||||
const request: ExportResources['requests'][0] = {
|
||||
model: 'http_request',
|
||||
id: generateId('rq'),
|
||||
workspaceId: workspace.id,
|
||||
folderId,
|
||||
name: v.name,
|
||||
method: r.method || 'GET',
|
||||
url: typeof r.url === 'string' ? r.url : toRecord(r.url).raw,
|
||||
body: bodyPatch.body,
|
||||
bodyType: bodyPatch.bodyType,
|
||||
authentication: authPatch.authentication,
|
||||
authenticationType: authPatch.authenticationType,
|
||||
headers: [
|
||||
...bodyPatch.headers,
|
||||
...authPatch.headers,
|
||||
...toArray(r.header).map((h) => {
|
||||
return {
|
||||
name: h.key,
|
||||
value: h.value,
|
||||
enabled: !h.disabled,
|
||||
};
|
||||
}),
|
||||
],
|
||||
};
|
||||
exportResources.requests.push(request);
|
||||
} else {
|
||||
console.log('Unknown item', v, folderId);
|
||||
}
|
||||
};
|
||||
|
||||
for (const item of root.item) {
|
||||
importItem(item);
|
||||
}
|
||||
|
||||
return { resources: convertTemplateSyntax(exportResources) };
|
||||
}
|
||||
|
||||
function importAuth(
|
||||
rawAuth: any,
|
||||
): Pick<HttpRequest, 'authentication' | 'authenticationType' | 'headers'> {
|
||||
const auth = toRecord(rawAuth);
|
||||
if ('basic' in auth) {
|
||||
return {
|
||||
headers: [],
|
||||
authenticationType: 'basic',
|
||||
authentication: {
|
||||
username: auth.basic.username || '',
|
||||
password: auth.basic.password || '',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// TODO: support other auth types
|
||||
return { headers: [], authenticationType: null, authentication: {} };
|
||||
}
|
||||
}
|
||||
|
||||
function importBody(rawBody: any): Pick<HttpRequest, 'body' | 'bodyType' | 'headers'> {
|
||||
const body = toRecord(rawBody);
|
||||
if ('graphql' in body) {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
bodyType: 'graphql',
|
||||
body: {
|
||||
text: JSON.stringify(
|
||||
{ query: body.graphql.query, variables: parseJSONToRecord(body.graphql.variables) },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
};
|
||||
} else if ('urlencoded' in body) {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: toArray(body.urlencoded).map((f) => ({
|
||||
enabled: !f.disabled,
|
||||
name: f.key ?? '',
|
||||
value: f.value ?? '',
|
||||
})),
|
||||
},
|
||||
};
|
||||
} else if ('formdata' in body) {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
name: 'Content-Type',
|
||||
value: 'multipart/form-data',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
bodyType: 'multipart/form-data',
|
||||
body: {
|
||||
form: toArray(body.formdata).map((f) =>
|
||||
f.src != null
|
||||
? {
|
||||
enabled: !f.disabled,
|
||||
name: f.key ?? '',
|
||||
file: f.src ?? '',
|
||||
}
|
||||
: {
|
||||
enabled: !f.disabled,
|
||||
name: f.key ?? '',
|
||||
value: f.value ?? '',
|
||||
},
|
||||
),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// TODO: support other body types
|
||||
return { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
}
|
||||
|
||||
function parseJSONToRecord(jsonStr: string): Record<string, any> | null {
|
||||
try {
|
||||
return toRecord(JSON.parse(jsonStr));
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function toRecord(value: any): Record<string, any> {
|
||||
if (Object.prototype.toString.call(value) === '[object Object]') return value;
|
||||
else return {};
|
||||
}
|
||||
|
||||
function toArray(value: any): any[] {
|
||||
if (Object.prototype.toString.call(value) === '[object Array]') return value;
|
||||
else return [];
|
||||
}
|
||||
|
||||
/** Recursively render all nested object properties */
|
||||
function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}') as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function generateId(prefix: 'wk' | 'rq' | 'fl'): string {
|
||||
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let id = `${prefix}_`;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
id += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
23
plugins/importer-postman/tsconfig.json
Normal file
23
plugins/importer-postman/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
]
|
||||
}
|
||||
13
plugins/importer-postman/vite.config.js
Normal file
13
plugins/importer-postman/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/importer-postman'),
|
||||
},
|
||||
});
|
||||
12
plugins/importer-yaak/package-lock.json
generated
Normal file
12
plugins/importer-yaak/package-lock.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "importer-yaak",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "importer-yaak",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
plugins/importer-yaak/package.json
Normal file
4
plugins/importer-yaak/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "importer-yaak",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
32
plugins/importer-yaak/src/index.js
Normal file
32
plugins/importer-yaak/src/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export function pluginHookImport(contents) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(contents);
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isJSObject(parsed)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!('yaakSchema' in parsed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate v1 to v2 -- changes requests to httpRequests
|
||||
if (parsed.yaakSchema === 1) {
|
||||
parsed.resources.httpRequests = parsed.resources.requests;
|
||||
parsed.yaakSchema = 2;
|
||||
}
|
||||
|
||||
if (parsed.yaakSchema === 2) {
|
||||
return { resources: parsed.resources }; // Should already be in the correct format
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isJSObject(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
||||
13
plugins/importer-yaak/vite.config.js
Normal file
13
plugins/importer-yaak/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/importer-yaak'),
|
||||
},
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package yaak.plugins.runtime;
|
||||
|
||||
service PluginRuntime {
|
||||
rpc hookImport (HookImportRequest) returns (HookResponse);
|
||||
rpc hookResponseFilter (HookResponseFilterRequest) returns (HookResponse);
|
||||
rpc hookExport (HookExportRequest) returns (HookResponse);
|
||||
}
|
||||
|
||||
message PluginInfo {
|
||||
string plugin = 1;
|
||||
}
|
||||
|
||||
message HookResponse {
|
||||
PluginInfo info = 1;
|
||||
string data = 2;
|
||||
}
|
||||
|
||||
message HookImportRequest {
|
||||
string data = 1;
|
||||
}
|
||||
|
||||
message HookResponseFilterRequest {
|
||||
string filter = 1;
|
||||
string body = 2;
|
||||
string contentType = 3;
|
||||
}
|
||||
|
||||
message HookExportRequest {
|
||||
string request = 1;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const version = process.env.YAAK_VERSION?.replace('v', '');
|
||||
if (!version) {
|
||||
throw new Error('YAAK_VERSION environment variable not set')
|
||||
}
|
||||
|
||||
const tauriConfigPath = path.join(__dirname, '../src-tauri/tauri.conf.json');
|
||||
const tauriConfig = JSON.parse(fs.readFileSync(tauriConfigPath, 'utf8'));
|
||||
|
||||
tauriConfig.version = version;
|
||||
|
||||
console.log('Writing version ' + version + ' to ' + tauriConfigPath)
|
||||
fs.writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2));
|
||||
@@ -1,62 +0,0 @@
|
||||
const path = require('node:path');
|
||||
const decompress = require('decompress');
|
||||
const Downloader = require("nodejs-file-downloader");
|
||||
const {rmSync, unlinkSync, cpSync, mkdirSync} = require("node:fs");
|
||||
|
||||
// `${process.platform}_${process.arch}`
|
||||
const MAC_ARM = 'darwin_arm64';
|
||||
const MAC_X64 = 'darwin_x64';
|
||||
const LNX_X64 = 'linux_x64';
|
||||
const WIN_X64 = 'win32_x64';
|
||||
|
||||
const URL_MAP = {
|
||||
[MAC_ARM]: 'https://nodejs.org/download/release/v22.5.1/node-v22.5.1-darwin-arm64.tar.gz',
|
||||
[MAC_X64]: 'https://nodejs.org/download/release/v22.5.1/node-v22.5.1-darwin-x64.tar.gz',
|
||||
[LNX_X64]: 'https://nodejs.org/download/release/v22.5.1/node-v22.5.1-linux-x64.tar.gz',
|
||||
[WIN_X64]: 'https://nodejs.org/download/release/v22.5.1/node-v22.5.1-win-x64.zip',
|
||||
};
|
||||
|
||||
const DST_BIN_MAP = {
|
||||
darwin_arm64: 'node-aarch64-apple-darwin',
|
||||
darwin_x64: 'node-x86_64-apple-darwin',
|
||||
linux_x64: 'node-x86_64-unknown-linux-gnu',
|
||||
win32_x64: 'node-x86_64-pc-windows-msvc.exe',
|
||||
};
|
||||
|
||||
const SRC_BIN_MAP = {
|
||||
[MAC_ARM]: 'node-v22.5.1-darwin-arm64/bin/node',
|
||||
[MAC_X64]: 'node-v22.5.1-darwin-x64/bin/node',
|
||||
[LNX_X64]: 'node-v22.5.1-linux-x64/bin/node',
|
||||
[WIN_X64]: 'node-v22.5.1-win-x64/node.exe',
|
||||
};
|
||||
|
||||
const dstDir = path.join(__dirname, `..`, 'src-tauri', 'vendored', 'node');
|
||||
rmSync(dstDir, {recursive: true, force: true});
|
||||
mkdirSync(dstDir, {recursive: true});
|
||||
|
||||
(async function () {
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
console.log('Vendoring NodeJS binary for', key);
|
||||
const url = URL_MAP[key];
|
||||
const tmpDir = path.join(__dirname, 'tmp', Date.now().toString());
|
||||
|
||||
// Download GitHub release artifact
|
||||
const {filePath} = await new Downloader({url, directory: tmpDir,}).download();
|
||||
|
||||
// Decompress to the same directory
|
||||
await decompress(filePath, tmpDir, {});
|
||||
|
||||
// Remove the original archive
|
||||
unlinkSync(filePath);
|
||||
|
||||
// Copy binary
|
||||
const binSrc = path.join(tmpDir, SRC_BIN_MAP[key]);
|
||||
const binDst = path.join(dstDir, DST_BIN_MAP[key]);
|
||||
cpSync(binSrc, binDst);
|
||||
rmSync(tmpDir, {recursive: true, force: true});
|
||||
|
||||
console.log("Downloaded NodeJS to", binDst);
|
||||
})().catch(err => {
|
||||
console.log('Script failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
const {readdirSync, cpSync} = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const {execSync} = require("node:child_process");
|
||||
const pluginsDir = process.env.YAAK_PLUGINS_DIR;
|
||||
if (!pluginsDir) {
|
||||
console.log("YAAK_PLUGINS_DIR is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Installing Yaak plugins dependencies', pluginsDir);
|
||||
execSync('npm ci', {cwd: pluginsDir});
|
||||
console.log('Building Yaak plugins', pluginsDir);
|
||||
execSync('npm run build', {cwd: pluginsDir});
|
||||
|
||||
console.log('Copying Yaak plugins to', pluginsDir);
|
||||
|
||||
const pluginsRoot = path.join(pluginsDir, 'plugins');
|
||||
for (const name of readdirSync(pluginsRoot)) {
|
||||
const dir = path.join(pluginsRoot, name);
|
||||
if (name.startsWith('.')) continue;
|
||||
const destDir = path.join(__dirname, '../src-tauri/vendored/plugins/', name);
|
||||
console.log(`Copying ${name} to ${destDir}`);
|
||||
cpSync(path.join(dir, 'package.json'), path.join(destDir, 'package.json'));
|
||||
cpSync(path.join(dir, 'build/index.js'), path.join(destDir, 'build/index.js'));
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
const decompress = require('decompress');
|
||||
const Downloader = require("nodejs-file-downloader");
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs");
|
||||
const {rmSync, mkdirSync} = require("node:fs");
|
||||
|
||||
// `${process.platform}_${process.arch}`
|
||||
const MAC_ARM = 'darwin_arm64';
|
||||
const MAC_X64 = 'darwin_x64';
|
||||
const LNX_X64 = 'linux_x64';
|
||||
const WIN_X64 = 'win32_x64';
|
||||
|
||||
const URL_MAP = {
|
||||
[MAC_ARM]: 'https://github.com/protocolbuffers/protobuf/releases/download/v27.2/protoc-27.2-osx-aarch_64.zip',
|
||||
[MAC_X64]: 'https://github.com/protocolbuffers/protobuf/releases/download/v27.2/protoc-27.2-osx-x86_64.zip',
|
||||
[LNX_X64]: 'https://github.com/protocolbuffers/protobuf/releases/download/v27.2/protoc-27.2-linux-x86_64.zip',
|
||||
[WIN_X64]: 'https://github.com/protocolbuffers/protobuf/releases/download/v27.2/protoc-27.2-win64.zip',
|
||||
};
|
||||
|
||||
const DST_BIN_MAP = {
|
||||
[MAC_ARM]: 'protoc-aarch64-apple-darwin',
|
||||
[MAC_X64]: 'protoc-x86_64-apple-darwin',
|
||||
[LNX_X64]: 'protoc-x86_64-unknown-linux-gnu',
|
||||
[WIN_X64]: 'protoc-x86_64-pc-windows-msvc.exe',
|
||||
};
|
||||
|
||||
const SRC_BIN_MAP = {
|
||||
[MAC_ARM]: 'bin/protoc',
|
||||
[MAC_X64]: 'bin/protoc',
|
||||
[LNX_X64]: 'bin/protoc',
|
||||
[WIN_X64]: 'bin/protoc.exe',
|
||||
};
|
||||
|
||||
const dstDir = path.join(__dirname, `..`, 'src-tauri', 'vendored', 'protoc');
|
||||
rmSync(dstDir, {recursive: true, force: true});
|
||||
mkdirSync(dstDir, {recursive: true});
|
||||
|
||||
(async function () {
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
console.log("Vendoring protoc binary for", key);
|
||||
const url = URL_MAP[key];
|
||||
const tmpDir = path.join(__dirname, 'tmp', Date.now().toString());
|
||||
|
||||
// Download GitHub release artifact
|
||||
const {filePath} = await new Downloader({url, directory: tmpDir,}).download();
|
||||
|
||||
// Decompress to the same directory
|
||||
await decompress(filePath, tmpDir, {});
|
||||
|
||||
// Remove the original archive
|
||||
fs.unlinkSync(filePath);
|
||||
|
||||
// Copy binary
|
||||
const binSrc = path.join(tmpDir, SRC_BIN_MAP[key]);
|
||||
const binDst = path.join(dstDir, DST_BIN_MAP[key]);
|
||||
fs.cpSync(binSrc, binDst);
|
||||
|
||||
// Copy other files
|
||||
const includeSrc = path.join(tmpDir, 'include');
|
||||
const includeDst = path.join(dstDir, 'include');
|
||||
fs.cpSync(includeSrc, includeDst, {recursive: true});
|
||||
|
||||
rmSync(tmpDir, {recursive: true, force: true});
|
||||
|
||||
console.log("Downloaded protoc to", binDst);
|
||||
})().catch(err => console.log('Script failed:', err));
|
||||
1
src-tauri/.gitignore
vendored
1
src-tauri/.gitignore
vendored
@@ -2,4 +2,3 @@
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
vendored
|
||||
|
||||
12
src-tauri/.sqlx/query-11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1.json
generated
Normal file
12
src-tauri/.sqlx/query-11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO http_requests (\n id, workspace_id, folder_id, name, url, url_parameters, method, body, body_type,\n authentication, authentication_type, headers, sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "11394af12419cca3be3a26dff9275514ea2a44504e3c7a568a9578c64b5713d1"
|
||||
}
|
||||
12
src-tauri/.sqlx/query-467b87ad1209a4653b1dc8462d79236a655240c5b402fa9fd75c12ebd9bb6b86.json
generated
Normal file
12
src-tauri/.sqlx/query-467b87ad1209a4653b1dc8462d79236a655240c5b402fa9fd75c12ebd9bb6b86.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n authentication_type, authentication, metadata\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication,\n metadata = excluded.metadata\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "467b87ad1209a4653b1dc8462d79236a655240c5b402fa9fd75c12ebd9bb6b86"
|
||||
}
|
||||
12
src-tauri/.sqlx/query-48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0.json
generated
Normal file
12
src-tauri/.sqlx/query-48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE settings SET (\n theme, appearance, update_channel\n ) = (?, ?, ?) WHERE id = 'default';\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "48ec5fdf20f34add763c540061caa25054545503704e19f149987f99b1a0e4f0"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO grpc_requests (\n id, name, workspace_id, folder_id, sort_priority, url, service, method, message,\n authentication_type, authentication, metadata\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n workspace_id = excluded.workspace_id,\n name = excluded.name,\n folder_id = excluded.folder_id,\n sort_priority = excluded.sort_priority,\n url = excluded.url,\n service = excluded.service,\n method = excluded.method,\n message = excluded.message,\n authentication_type = excluded.authentication_type,\n authentication = excluded.authentication,\n metadata = excluded.metadata\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5af82cd333895d3d7d67a92f37b0feb338f615b88aea2bd09cb5809008c645a3"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO http_requests (\n id, workspace_id, folder_id, name, url, url_parameters, method, body, body_type,\n authentication, authentication_type, headers, sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n workspace_id = excluded.workspace_id,\n name = excluded.name,\n folder_id = excluded.folder_id,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n url_parameters = excluded.url_parameters,\n sort_priority = excluded.sort_priority\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5f2f40062abbe93e23b38876319cf16d4d2b3f8d0be32ffe7848528c725e1429"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE settings SET (\n theme, appearance, theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,\n open_workspace_new_window\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 10
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6b5edf45a6799cd7f87c23a3c7f818ad110d58c601f694a619d9345ae9e8e11d"
|
||||
}
|
||||
56
src-tauri/.sqlx/query-b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb.json
generated
Normal file
56
src-tauri/.sqlx/query-b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb.json
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance, update_channel\n FROM settings\n WHERE id = 'default'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "appearance",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_channel",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b32994b09ae7a06eb0f031069d327e55127a5bce60cbb499b83d1701386a23cb"
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id, model, created_at, updated_at, theme, appearance,\n theme_dark, theme_light, update_channel,\n interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,\n open_workspace_new_window\n FROM settings\n WHERE id = 'default'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "appearance",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "theme_dark",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "theme_light",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_channel",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "interface_font_size",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "interface_scale",
|
||||
"ordinal": 10,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "editor_font_size",
|
||||
"ordinal": 11,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "editor_soft_wrap",
|
||||
"ordinal": 12,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "open_workspace_new_window",
|
||||
"ordinal": 13,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "daa61066517df649e7c80a8ce407839ad502e8e5e43aa8c02e049865acbbae75"
|
||||
}
|
||||
4501
src-tauri/Cargo.lock
generated
4501
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,18 @@
|
||||
workspace = { members = ["grpc", "templates", "tauri-plugin-plugin-runtime"] }
|
||||
|
||||
workspace = { members = ["grpc"] }
|
||||
[package]
|
||||
name = "yaak-app"
|
||||
version = "0.0.0"
|
||||
description = "A network protocol testing utility app"
|
||||
authors = ["Gregory Schier"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gschier/yaak-app"
|
||||
edition = "2021"
|
||||
|
||||
# Produce a library for mobile support
|
||||
[lib]
|
||||
name = "tauri_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "lib"]
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
@@ -24,35 +22,53 @@ cocoa = "0.25.0"
|
||||
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
|
||||
|
||||
[dependencies]
|
||||
grpc = { path = "./grpc" }
|
||||
templates = { path = "./templates" }
|
||||
plugin_runtime = { path = "tauri-plugin-plugin-runtime" }
|
||||
anyhow = "1.0.86"
|
||||
base64 = "0.22.0"
|
||||
base64 = "0.21.0"
|
||||
boa_engine = { version = "0.17.3", features = ["annex-b"] }
|
||||
boa_runtime = { version = "0.17.3" }
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
datetime = "0.5.2"
|
||||
hex_color = "3.0.0"
|
||||
http = "1"
|
||||
log = "0.4.21"
|
||||
futures = "0.3.26"
|
||||
http = "0.2.8"
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.2"
|
||||
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
|
||||
reqwest_cookie_store = "0.8.0"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = { version = "1.0.116", features = ["raw_value"] }
|
||||
serde_yaml = "0.9.34"
|
||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
||||
tauri = { version = "2.0.0-beta", features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.1.0-beta"
|
||||
tauri-plugin-dialog = "2.0.0-beta"
|
||||
tauri-plugin-fs = "2.0.0-beta"
|
||||
tauri-plugin-log = { version = "2.0.0-beta", features = ["colored"] }
|
||||
tauri-plugin-os = "2.0.0-beta"
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri-plugin-updater = "2.0.0-beta"
|
||||
tauri-plugin-window-state = "2.0.0-beta"
|
||||
tokio = { version = "1.36.0", features = ["sync"] }
|
||||
tokio-stream = "0.1.15"
|
||||
uuid = "1.7.0"
|
||||
thiserror = "1.0.61"
|
||||
mime_guess = "2.0.5"
|
||||
reqwest = { version = "0.11.23", features = ["multipart", "cookies", "gzip", "brotli", "deflate"] }
|
||||
cookie = { version = "0.18.0" }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = { version = "1.0.111", features = ["raw_value"] }
|
||||
sqlx = { version = "0.7.3", features = ["sqlite", "runtime-tokio-rustls", "json", "chrono", "time"] }
|
||||
tauri = { version = "1.5.4", features = [
|
||||
"config-toml",
|
||||
"path-all",
|
||||
"devtools",
|
||||
"dialog-open",
|
||||
"dialog-save",
|
||||
"fs-read-file",
|
||||
"os-all",
|
||||
"protocol-asset",
|
||||
"shell-open",
|
||||
"shell-sidecar",
|
||||
"updater",
|
||||
"window-close",
|
||||
"window-maximize",
|
||||
"window-minimize",
|
||||
"window-set-decorations",
|
||||
"window-set-title",
|
||||
"window-start-dragging",
|
||||
"window-unmaximize",
|
||||
] }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1", features = ["colored"] }
|
||||
tokio = { version = "1.25.0", features = ["sync"] }
|
||||
uuid = "1.3.0"
|
||||
log = "0.4.20"
|
||||
datetime = "0.5.2"
|
||||
window-shadows = "0.2.2"
|
||||
reqwest_cookie_store = "0.6.0"
|
||||
grpc = { path = "./grpc" }
|
||||
tokio-stream = "0.1.14"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||
default = ["custom-protocol"]
|
||||
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/capabilities.json",
|
||||
"identifier": "main",
|
||||
"description": "Main permissions",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"*"
|
||||
],
|
||||
"permissions": [
|
||||
"os:allow-os-type",
|
||||
"event:allow-emit",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"event:allow-listen",
|
||||
"event:allow-unlisten",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-read-text-file",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
{
|
||||
"path": "$APPDATA"
|
||||
},
|
||||
{
|
||||
"path": "$APPDATA/**"
|
||||
}
|
||||
]
|
||||
},
|
||||
"shell:allow-open",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{
|
||||
"name": "protoc",
|
||||
"sidecar": true,
|
||||
"args": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"webview:allow-set-webview-zoom",
|
||||
"window:allow-close",
|
||||
"window:allow-is-fullscreen",
|
||||
"window:allow-maximize",
|
||||
"window:allow-minimize",
|
||||
"window:allow-toggle-maximize",
|
||||
"window:allow-set-decorations",
|
||||
"window:allow-set-title",
|
||||
"window:allow-start-dragging",
|
||||
"window:allow-unmaximize",
|
||||
"window:allow-theme",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"clipboard-manager:allow-write-text"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["os:allow-os-type","event:allow-emit","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","event:allow-listen","event:allow-unlisten","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":true,"name":"protoc","sidecar":true}]},"webview:allow-set-webview-zoom","window:allow-close","window:allow-is-fullscreen","window:allow-maximize","window:allow-minimize","window:allow-toggle-maximize","window:allow-set-decorations","window:allow-set-title","window:allow-start-dragging","window:allow-unmaximize","window:allow-theme","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,10 @@ serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
prost-reflect = { version = "0.12.0", features = ["serde", "derive"] }
|
||||
log = "0.4.20"
|
||||
once_cell = { version = "1.19.0", features = [] }
|
||||
anyhow = "1.0.79"
|
||||
hyper = { version = "0.14" }
|
||||
hyper-rustls = { version = "0.24.0", features = ["http2"] }
|
||||
protoc-bin-vendored = "3.0.0"
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
tauri = { version = "2.0.0-beta" }
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
md5 = "0.7.0"
|
||||
tauri = { version = "1.5.4", features = ["process-command-api"]}
|
||||
|
||||
@@ -8,7 +8,6 @@ use hyper_rustls::HttpsConnector;
|
||||
pub use prost_reflect::DynamicMessage;
|
||||
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
|
||||
use serde_json::Deserializer;
|
||||
use tauri::AppHandle;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::body::BoxBody;
|
||||
use tonic::metadata::{MetadataKey, MetadataValue};
|
||||
@@ -16,9 +15,7 @@ use tonic::transport::Uri;
|
||||
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
|
||||
|
||||
use crate::codec::DynamicCodec;
|
||||
use crate::proto::{
|
||||
fill_pool_from_files, fill_pool_from_reflection, get_transport, method_desc_to_path,
|
||||
};
|
||||
use crate::proto::{fill_pool, fill_pool_from_files, get_transport, method_desc_to_path};
|
||||
use crate::{json_schema, MethodDefinition, ServiceDefinition};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -169,52 +166,39 @@ impl GrpcConnection {
|
||||
}
|
||||
|
||||
pub struct GrpcHandle {
|
||||
app_handle: AppHandle,
|
||||
pools: HashMap<String, DescriptorPool>,
|
||||
}
|
||||
|
||||
impl GrpcHandle {
|
||||
pub fn new(app_handle: &AppHandle) -> Self {
|
||||
impl Default for GrpcHandle {
|
||||
fn default() -> Self {
|
||||
let pools = HashMap::new();
|
||||
Self {
|
||||
pools,
|
||||
app_handle: app_handle.clone(),
|
||||
}
|
||||
Self { pools }
|
||||
}
|
||||
}
|
||||
|
||||
impl GrpcHandle {
|
||||
pub async fn reflect(
|
||||
pub async fn services_from_files(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
proto_files: &Vec<PathBuf>,
|
||||
) -> Result<(), String> {
|
||||
let pool = if proto_files.is_empty() {
|
||||
let full_uri = uri_from_str(uri)?;
|
||||
fill_pool_from_reflection(&full_uri).await
|
||||
} else {
|
||||
fill_pool_from_files(&self.app_handle, proto_files).await
|
||||
}?;
|
||||
|
||||
self.pools
|
||||
.insert(make_pool_key(id, uri, proto_files), pool.clone());
|
||||
Ok(())
|
||||
uri: &Uri,
|
||||
paths: Vec<PathBuf>,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
let pool = fill_pool_from_files(paths).await?;
|
||||
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||
Ok(self.services_from_pool(&pool))
|
||||
}
|
||||
pub async fn services_from_reflection(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &Uri,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
let pool = fill_pool(uri).await?;
|
||||
self.pools.insert(self.get_pool_key(id, uri), pool.clone());
|
||||
Ok(self.services_from_pool(&pool))
|
||||
}
|
||||
|
||||
pub async fn services(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
proto_files: &Vec<PathBuf>,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
// Ensure reflection is up-to-date
|
||||
self.reflect(id, uri, proto_files).await?;
|
||||
|
||||
let pool = self
|
||||
.get_pool(id, uri, proto_files)
|
||||
.ok_or("Failed to get pool".to_string())?;
|
||||
Ok(self.services_from_pool(&pool))
|
||||
fn get_pool_key(&self, id: &str, uri: &Uri) -> String {
|
||||
format!("{}-{}", id, uri)
|
||||
}
|
||||
|
||||
fn services_from_pool(&self, pool: &DescriptorPool) -> Vec<ServiceDefinition> {
|
||||
@@ -245,26 +229,24 @@ impl GrpcHandle {
|
||||
pub async fn connect(
|
||||
&mut self,
|
||||
id: &str,
|
||||
uri: &str,
|
||||
proto_files: &Vec<PathBuf>,
|
||||
uri: Uri,
|
||||
proto_files: Vec<PathBuf>,
|
||||
) -> Result<GrpcConnection, String> {
|
||||
self.reflect(id, uri, proto_files).await?;
|
||||
let pool = self
|
||||
.get_pool(id, uri, proto_files)
|
||||
.ok_or("Failed to get pool")?;
|
||||
|
||||
let uri = uri_from_str(uri)?;
|
||||
let conn = get_transport();
|
||||
let connection = GrpcConnection {
|
||||
pool: pool.clone(),
|
||||
conn,
|
||||
uri,
|
||||
let pool = match self.pools.get(id) {
|
||||
Some(p) => p.clone(),
|
||||
None => match proto_files.len() {
|
||||
0 => fill_pool(&uri).await?,
|
||||
_ => {
|
||||
let pool = fill_pool_from_files(proto_files).await?;
|
||||
self.pools.insert(id.to_string(), pool.clone());
|
||||
pool
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
fn get_pool(&self, id: &str, uri: &str, proto_files: &Vec<PathBuf>) -> Option<&DescriptorPool> {
|
||||
self.pools.get(make_pool_key(id, uri, proto_files).as_str())
|
||||
let conn = get_transport();
|
||||
let connection = GrpcConnection { pool, conn, uri };
|
||||
Ok(connection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,28 +259,3 @@ fn decorate_req<T>(metadata: HashMap<String, String>, req: &mut Request<T>) -> R
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uri_from_str(uri_str: &str) -> Result<Uri, String> {
|
||||
match Uri::from_str(uri_str) {
|
||||
Ok(uri) => Ok(uri),
|
||||
Err(err) => {
|
||||
// Uri::from_str basically only returns "invalid format" so we add more context here
|
||||
Err(format!("Failed to parse URL, {}", err.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pool_key(id: &str, uri: &str, proto_files: &Vec<PathBuf>) -> String {
|
||||
let pool_key = format!(
|
||||
"{}::{}::{}",
|
||||
id,
|
||||
uri,
|
||||
proto_files
|
||||
.iter()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(":")
|
||||
);
|
||||
|
||||
format!("{:x}", md5::compute(pool_key))
|
||||
}
|
||||
|
||||
@@ -1,48 +1,36 @@
|
||||
use std::env::temp_dir;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::str::{from_utf8, FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::Client;
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
||||
use log::{debug, info, warn};
|
||||
use prost::Message;
|
||||
use prost_reflect::{DescriptorPool, MethodDescriptor};
|
||||
use prost_types::{FileDescriptorProto, FileDescriptorSet};
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_shell::process::CommandEvent;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use tauri::api::process::{Command, CommandEvent};
|
||||
use tokio::fs;
|
||||
use tokio_stream::StreamExt;
|
||||
use tonic::body::BoxBody;
|
||||
use tonic::codegen::http::uri::PathAndQuery;
|
||||
use tonic::transport::Uri;
|
||||
use tonic::Request;
|
||||
use tonic::transport::Uri;
|
||||
use tonic_reflection::pb::server_reflection_client::ServerReflectionClient;
|
||||
use tonic_reflection::pb::server_reflection_request::MessageRequest;
|
||||
use tonic_reflection::pb::server_reflection_response::MessageResponse;
|
||||
use tonic_reflection::pb::ServerReflectionRequest;
|
||||
|
||||
pub async fn fill_pool_from_files(
|
||||
app_handle: &AppHandle,
|
||||
paths: &Vec<PathBuf>,
|
||||
) -> Result<DescriptorPool, String> {
|
||||
pub async fn fill_pool_from_files(paths: Vec<PathBuf>) -> Result<DescriptorPool, String> {
|
||||
let mut pool = DescriptorPool::new();
|
||||
let random_file_name = format!("{}.desc", uuid::Uuid::new_v4());
|
||||
let desc_path = temp_dir().join(random_file_name);
|
||||
let global_import_dir = app_handle
|
||||
.path()
|
||||
.resolve("protoc-include", BaseDirectory::Resource)
|
||||
.expect("failed to resolve protoc include directory");
|
||||
|
||||
let mut args = vec![
|
||||
"--include_imports".to_string(),
|
||||
"--include_source_info".to_string(),
|
||||
"-I".to_string(),
|
||||
global_import_dir.to_string_lossy().to_string(),
|
||||
"-o".to_string(),
|
||||
desc_path.to_string_lossy().to_string(),
|
||||
];
|
||||
@@ -65,9 +53,7 @@ pub async fn fill_pool_from_files(
|
||||
}
|
||||
}
|
||||
|
||||
let (mut rx, _child) = app_handle
|
||||
.shell()
|
||||
.sidecar("protoc")
|
||||
let (mut rx, _child) = Command::new_sidecar("protoc")
|
||||
.expect("protoc not found")
|
||||
.args(args)
|
||||
.spawn()
|
||||
@@ -76,16 +62,10 @@ pub async fn fill_pool_from_files(
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => {
|
||||
info!(
|
||||
"protoc stdout: {}",
|
||||
from_utf8(line.as_slice()).unwrap_or_default().to_string()
|
||||
);
|
||||
info!("protoc stdout: {}", line);
|
||||
}
|
||||
CommandEvent::Stderr(line) => {
|
||||
info!(
|
||||
"protoc stderr: {}",
|
||||
from_utf8(line.as_slice()).unwrap_or_default().to_string()
|
||||
);
|
||||
info!("protoc stderr: {}", line);
|
||||
}
|
||||
CommandEvent::Error(e) => {
|
||||
return Err(e.to_string());
|
||||
@@ -96,7 +76,10 @@ pub async fn fill_pool_from_files(
|
||||
// success
|
||||
}
|
||||
Some(code) => {
|
||||
return Err(format!("protoc failed with exit code: {}", code,));
|
||||
return Err(format!(
|
||||
"protoc failed with exit code: {}",
|
||||
code,
|
||||
));
|
||||
}
|
||||
None => {
|
||||
return Err("protoc failed with no exit code".to_string());
|
||||
@@ -121,7 +104,7 @@ pub async fn fill_pool_from_files(
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, String> {
|
||||
pub async fn fill_pool(uri: &Uri) -> Result<DescriptorPool, String> {
|
||||
let mut pool = DescriptorPool::new();
|
||||
let mut client = ServerReflectionClient::with_origin(get_transport(), uri.clone());
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Enable for v8 execution -->
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
<!-- <key>com.apple.security.network.client</key> <true/>-->
|
||||
</dict>
|
||||
<dict>
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
<!-- <key>com.apple.security.network.client</key> <true/>-->
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN theme_dark TEXT DEFAULT 'yaak-dark' NOT NULL;
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN theme_light TEXT DEFAULT 'yaak-light' NOT NULL;
|
||||
@@ -1,4 +0,0 @@
|
||||
ALTER TABLE settings ADD COLUMN interface_font_size INTEGER DEFAULT 15 NOT NULL;
|
||||
ALTER TABLE settings ADD COLUMN interface_scale INTEGER DEFAULT 1 NOT NULL;
|
||||
ALTER TABLE settings ADD COLUMN editor_font_size INTEGER DEFAULT 13 NOT NULL;
|
||||
ALTER TABLE settings ADD COLUMN editor_soft_wrap BOOLEAN DEFAULT 1 NOT NULL;
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE settings ADD COLUMN open_workspace_new_window BOOLEAN NULL DEFAULT NULL;
|
||||
3191
src-tauri/plugins/filter-jsonpath/index.mjs
Normal file
3191
src-tauri/plugins/filter-jsonpath/index.mjs
Normal file
File diff suppressed because it is too large
Load Diff
6125
src-tauri/plugins/filter-xpath/index.mjs
Normal file
6125
src-tauri/plugins/filter-xpath/index.mjs
Normal file
File diff suppressed because it is too large
Load Diff
134
src-tauri/plugins/importer-insomnia/index.mjs
Normal file
134
src-tauri/plugins/importer-insomnia/index.mjs
Normal file
@@ -0,0 +1,134 @@
|
||||
function S(e, t) {
|
||||
return console.log("IMPORTING Environment", e._id, e.name, JSON.stringify(e, null, 2)), {
|
||||
id: e._id,
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
workspaceId: t,
|
||||
model: "environment",
|
||||
name: e.name,
|
||||
variables: Object.entries(e.data).map(([n, a]) => ({
|
||||
enabled: !0,
|
||||
name: n,
|
||||
value: `${a}`
|
||||
}))
|
||||
};
|
||||
}
|
||||
function I(e) {
|
||||
return m(e) && e._type === "workspace";
|
||||
}
|
||||
function y(e) {
|
||||
return m(e) && e._type === "request_group";
|
||||
}
|
||||
function g(e) {
|
||||
return m(e) && e._type === "request";
|
||||
}
|
||||
function f(e) {
|
||||
return m(e) && e._type === "environment";
|
||||
}
|
||||
function m(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Object]";
|
||||
}
|
||||
function w(e) {
|
||||
return Object.prototype.toString.call(e) === "[object String]";
|
||||
}
|
||||
function O(e) {
|
||||
return Object.entries(e).map(([t, n]) => ({
|
||||
enabled: !0,
|
||||
name: t,
|
||||
value: `${n}`
|
||||
}));
|
||||
}
|
||||
function l(e) {
|
||||
return w(e) ? e.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : e;
|
||||
}
|
||||
function h(e, t, n = 0) {
|
||||
var c, o;
|
||||
console.log("IMPORTING REQUEST", e._id, e.name, JSON.stringify(e, null, 2));
|
||||
let a = null, r = null;
|
||||
((c = e.body) == null ? void 0 : c.mimeType) === "application/graphql" ? (a = "graphql", r = l(e.body.text)) : ((o = e.body) == null ? void 0 : o.mimeType) === "application/json" && (a = "application/json", r = l(e.body.text));
|
||||
let i = null, u = {};
|
||||
return e.authentication.type === "bearer" ? (i = "bearer", u = {
|
||||
token: l(e.authentication.token)
|
||||
}) : e.authentication.type === "basic" && (i = "basic", u = {
|
||||
username: l(e.authentication.username),
|
||||
password: l(e.authentication.password)
|
||||
}), {
|
||||
id: e._id,
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
workspaceId: t,
|
||||
folderId: e.parentId === t ? null : e.parentId,
|
||||
model: "http_request",
|
||||
sortPriority: n,
|
||||
name: e.name,
|
||||
url: l(e.url),
|
||||
body: r,
|
||||
bodyType: a,
|
||||
authentication: u,
|
||||
authenticationType: i,
|
||||
method: e.method,
|
||||
headers: (e.headers ?? []).map(({ name: d, value: p, disabled: s }) => ({
|
||||
enabled: !s,
|
||||
name: d,
|
||||
value: p
|
||||
})).filter(({ name: d, value: p }) => d !== "" || p !== "")
|
||||
};
|
||||
}
|
||||
function _(e, t) {
|
||||
return console.log("IMPORTING FOLDER", e._id, e.name, JSON.stringify(e, null, 2)), {
|
||||
id: e._id,
|
||||
createdAt: new Date(e.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(e.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
folderId: e.parentId === t ? null : e.parentId,
|
||||
workspaceId: t,
|
||||
model: "folder",
|
||||
name: e.name
|
||||
};
|
||||
}
|
||||
function b(e) {
|
||||
console.log("RUNNING INSOMNIA");
|
||||
let t;
|
||||
try {
|
||||
t = JSON.parse(e);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!m(t) || !Array.isArray(t.resources))
|
||||
return;
|
||||
const n = {
|
||||
workspaces: [],
|
||||
requests: [],
|
||||
environments: [],
|
||||
folders: []
|
||||
}, a = t.resources.filter(I);
|
||||
for (const r of a) {
|
||||
const i = t.resources.find(
|
||||
(o) => f(o) && o.parentId === r._id
|
||||
);
|
||||
n.workspaces.push({
|
||||
id: r._id,
|
||||
createdAt: new Date(a.created ?? Date.now()).toISOString().replace("Z", ""),
|
||||
updatedAt: new Date(a.updated ?? Date.now()).toISOString().replace("Z", ""),
|
||||
model: "workspace",
|
||||
name: r.name,
|
||||
variables: i ? O(i.data) : []
|
||||
});
|
||||
const u = t.resources.filter(
|
||||
(o) => f(o) && o.parentId === (i == null ? void 0 : i._id)
|
||||
);
|
||||
n.environments.push(
|
||||
...u.map((o) => S(o, r._id))
|
||||
);
|
||||
const c = (o) => {
|
||||
const d = t.resources.filter((s) => s.parentId === o);
|
||||
let p = 0;
|
||||
for (const s of d)
|
||||
y(s) ? (n.folders.push(_(s, r._id)), c(s._id)) : g(s) && n.requests.push(h(s, r._id, p++));
|
||||
};
|
||||
c(r._id);
|
||||
}
|
||||
return n.requests = n.requests.filter(Boolean), n.environments = n.environments.filter(Boolean), n.workspaces = n.workspaces.filter(Boolean), { resources: n };
|
||||
}
|
||||
export {
|
||||
b as pluginHookImport
|
||||
};
|
||||
160
src-tauri/plugins/importer-postman/index.mjs
Normal file
160
src-tauri/plugins/importer-postman/index.mjs
Normal file
@@ -0,0 +1,160 @@
|
||||
const T = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", w = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", A = [w, T];
|
||||
function q(e) {
|
||||
const t = b(e);
|
||||
if (t == null)
|
||||
return;
|
||||
const n = a(t.info);
|
||||
if (!A.includes(n.schema) || !Array.isArray(t.item))
|
||||
return;
|
||||
const i = {
|
||||
workspaces: [],
|
||||
environments: [],
|
||||
requests: [],
|
||||
folders: []
|
||||
}, c = {
|
||||
model: "workspace",
|
||||
id: m("wk"),
|
||||
name: n.name || "Postman Import",
|
||||
description: n.description || ""
|
||||
};
|
||||
i.workspaces.push(c);
|
||||
const f = (r, u = null) => {
|
||||
if (typeof r.name == "string" && Array.isArray(r.item)) {
|
||||
const o = {
|
||||
model: "folder",
|
||||
workspaceId: c.id,
|
||||
id: m("fl"),
|
||||
name: r.name,
|
||||
folderId: u
|
||||
};
|
||||
i.folders.push(o);
|
||||
for (const s of r.item)
|
||||
f(s, o.id);
|
||||
} else if (typeof r.name == "string" && "request" in r) {
|
||||
const o = a(r.request), s = k(o.body), d = S(o.auth), g = {
|
||||
model: "http_request",
|
||||
id: m("rq"),
|
||||
workspaceId: c.id,
|
||||
folderId: u,
|
||||
name: r.name,
|
||||
method: o.method || "GET",
|
||||
url: typeof o.url == "string" ? o.url : a(o.url).raw,
|
||||
body: s.body,
|
||||
bodyType: s.bodyType,
|
||||
authentication: d.authentication,
|
||||
authenticationType: d.authenticationType,
|
||||
headers: [
|
||||
...s.headers,
|
||||
...d.headers,
|
||||
...y(o.header).map((p) => ({
|
||||
name: p.key,
|
||||
value: p.value,
|
||||
enabled: !p.disabled
|
||||
}))
|
||||
]
|
||||
};
|
||||
i.requests.push(g);
|
||||
} else
|
||||
console.log("Unknown item", r, u);
|
||||
};
|
||||
for (const r of t.item)
|
||||
f(r);
|
||||
return { resources: h(i) };
|
||||
}
|
||||
function S(e) {
|
||||
const t = a(e);
|
||||
return "basic" in t ? {
|
||||
headers: [],
|
||||
authenticationType: "basic",
|
||||
authentication: {
|
||||
username: t.basic.username || "",
|
||||
password: t.basic.password || ""
|
||||
}
|
||||
} : { headers: [], authenticationType: null, authentication: {} };
|
||||
}
|
||||
function k(e) {
|
||||
const t = a(e);
|
||||
return "graphql" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "application/json",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "graphql",
|
||||
body: {
|
||||
text: JSON.stringify(
|
||||
{ query: t.graphql.query, variables: b(t.graphql.variables) },
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
} : "urlencoded" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "application/x-www-form-urlencoded",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "application/x-www-form-urlencoded",
|
||||
body: {
|
||||
form: y(t.urlencoded).map((n) => ({
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
value: n.value ?? ""
|
||||
}))
|
||||
}
|
||||
} : "formdata" in t ? {
|
||||
headers: [
|
||||
{
|
||||
name: "Content-Type",
|
||||
value: "multipart/form-data",
|
||||
enabled: !0
|
||||
}
|
||||
],
|
||||
bodyType: "multipart/form-data",
|
||||
body: {
|
||||
form: y(t.formdata).map(
|
||||
(n) => n.src != null ? {
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
file: n.src ?? ""
|
||||
} : {
|
||||
enabled: !n.disabled,
|
||||
name: n.key ?? "",
|
||||
value: n.value ?? ""
|
||||
}
|
||||
)
|
||||
}
|
||||
} : { headers: [], bodyType: null, body: {} };
|
||||
}
|
||||
function b(e) {
|
||||
try {
|
||||
return a(JSON.parse(e));
|
||||
} catch {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function a(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Object]" ? e : {};
|
||||
}
|
||||
function y(e) {
|
||||
return Object.prototype.toString.call(e) === "[object Array]" ? e : [];
|
||||
}
|
||||
function h(e) {
|
||||
return typeof e == "string" ? e.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}") : Array.isArray(e) && e != null ? e.map(h) : typeof e == "object" && e != null ? Object.fromEntries(
|
||||
Object.entries(e).map(([t, n]) => [t, h(n)])
|
||||
) : e;
|
||||
}
|
||||
function m(e) {
|
||||
const t = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let n = `${e}_`;
|
||||
for (let l = 0; l < 10; l++)
|
||||
n += t[Math.floor(Math.random() * t.length)];
|
||||
return n;
|
||||
}
|
||||
export {
|
||||
q as pluginHookImport
|
||||
};
|
||||
17
src-tauri/plugins/importer-yaak/index.mjs
Normal file
17
src-tauri/plugins/importer-yaak/index.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
function u(r) {
|
||||
let e;
|
||||
try {
|
||||
e = JSON.parse(r);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (t(e) && "yaakSchema" in e && (e.yaakSchema === 1 && (e.resources.httpRequests = e.resources.requests, e.yaakSchema = 2), e.yaakSchema === 2))
|
||||
return { resources: e.resources };
|
||||
}
|
||||
function t(r) {
|
||||
return Object.prototype.toString.call(r) === "[object Object]";
|
||||
}
|
||||
export {
|
||||
t as isJSObject,
|
||||
u as pluginHookImport
|
||||
};
|
||||
BIN
src-tauri/protoc-vendored/protoc-aarch64-apple-darwin
Executable file
BIN
src-tauri/protoc-vendored/protoc-aarch64-apple-darwin
Executable file
Binary file not shown.
BIN
src-tauri/protoc-vendored/protoc-x86_64-apple-darwin
Executable file
BIN
src-tauri/protoc-vendored/protoc-x86_64-apple-darwin
Executable file
Binary file not shown.
BIN
src-tauri/protoc-vendored/protoc-x86_64-pc-windows-msvc.exe
Executable file
BIN
src-tauri/protoc-vendored/protoc-x86_64-pc-windows-msvc.exe
Executable file
Binary file not shown.
BIN
src-tauri/protoc-vendored/protoc-x86_64-unknown-linux-gnu
Executable file
BIN
src-tauri/protoc-vendored/protoc-x86_64-unknown-linux-gnu
Executable file
Binary file not shown.
@@ -1,25 +1,19 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use log::{debug, info};
|
||||
use log::{debug, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::types::JsonValue;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use crate::is_dev;
|
||||
use crate::models::{
|
||||
generate_id, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string,
|
||||
};
|
||||
|
||||
const NAMESPACE: &str = "analytics";
|
||||
const NUM_LAUNCHES_KEY: &str = "num_launches";
|
||||
use crate::models::{generate_id, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string};
|
||||
|
||||
// serializable
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AnalyticsResource {
|
||||
App,
|
||||
Appearance,
|
||||
CookieJar,
|
||||
Dialog,
|
||||
Environment,
|
||||
@@ -30,9 +24,7 @@ pub enum AnalyticsResource {
|
||||
HttpRequest,
|
||||
HttpResponse,
|
||||
KeyValue,
|
||||
Setting,
|
||||
Sidebar,
|
||||
Theme,
|
||||
Workspace,
|
||||
}
|
||||
|
||||
@@ -98,19 +90,20 @@ pub struct LaunchEventInfo {
|
||||
pub num_launches: i32,
|
||||
}
|
||||
|
||||
pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
|
||||
pub async fn track_launch_event(app_handle: &AppHandle) -> LaunchEventInfo {
|
||||
let namespace = "analytics";
|
||||
let last_tracked_version_key = "last_tracked_version";
|
||||
|
||||
let mut info = LaunchEventInfo::default();
|
||||
|
||||
info.num_launches = get_num_launches(app).await + 1;
|
||||
info.num_launches = get_key_value_int(app_handle, namespace, "num_launches", 0).await + 1;
|
||||
info.previous_version =
|
||||
get_key_value_string(app, NAMESPACE, last_tracked_version_key, "").await;
|
||||
info.current_version = app.package_info().version.to_string();
|
||||
get_key_value_string(app_handle, namespace, last_tracked_version_key, "").await;
|
||||
info.current_version = app_handle.package_info().version.to_string();
|
||||
|
||||
if info.previous_version.is_empty() {
|
||||
track_event(
|
||||
app,
|
||||
app_handle,
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::LaunchFirst,
|
||||
None,
|
||||
@@ -120,10 +113,10 @@ pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
|
||||
info.launched_after_update = info.current_version != info.previous_version;
|
||||
if info.launched_after_update {
|
||||
track_event(
|
||||
app,
|
||||
app_handle,
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::LaunchUpdate,
|
||||
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
|
||||
Some(json!({ "num_launches": info.num_launches })),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -131,23 +124,23 @@ pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
|
||||
|
||||
// Track a launch event in all cases
|
||||
track_event(
|
||||
app,
|
||||
app_handle,
|
||||
AnalyticsResource::App,
|
||||
AnalyticsAction::Launch,
|
||||
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
|
||||
Some(json!({ "num_launches": info.num_launches })),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Update key values
|
||||
|
||||
set_key_value_string(
|
||||
app,
|
||||
NAMESPACE,
|
||||
app_handle,
|
||||
namespace,
|
||||
last_tracked_version_key,
|
||||
info.current_version.as_str(),
|
||||
)
|
||||
.await;
|
||||
set_key_value_int(app, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches).await;
|
||||
set_key_value_int(app_handle, namespace, "num_launches", info.num_launches).await;
|
||||
|
||||
info
|
||||
}
|
||||
@@ -189,12 +182,15 @@ pub async fn track_event(
|
||||
|
||||
// Disable analytics actual sending in dev
|
||||
if is_dev() {
|
||||
debug!("track: {} {}", event, attributes_json);
|
||||
debug!("track: {} {} {:?}", event, attributes_json, params);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = req.send().await {
|
||||
info!("Error sending analytics event: {}", e);
|
||||
warn!(
|
||||
"Error sending analytics event: {} {} {} {:?}",
|
||||
e, event, attributes_json, params,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +207,7 @@ fn get_os() -> &'static str {
|
||||
}
|
||||
|
||||
fn get_window_size(app_handle: &AppHandle) -> String {
|
||||
let window = match app_handle.webview_windows().into_values().next() {
|
||||
let window = match app_handle.windows().into_values().next() {
|
||||
Some(w) => w,
|
||||
None => return "unknown".to_string(),
|
||||
};
|
||||
@@ -236,14 +232,10 @@ fn get_window_size(app_handle: &AppHandle) -> String {
|
||||
async fn get_id(app_handle: &AppHandle) -> String {
|
||||
let id = get_key_value_string(app_handle, "analytics", "id", "").await;
|
||||
if id.is_empty() {
|
||||
let new_id = generate_id();
|
||||
let new_id = generate_id(None);
|
||||
set_key_value_string(app_handle, "analytics", "id", new_id.as_str()).await;
|
||||
new_id
|
||||
} else {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_num_launches(app: &AppHandle) -> i32 {
|
||||
get_key_value_int(app, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::op2;
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_yaml_parse(#[string] text: String) -> Result<serde_json::Value, AnyError> {
|
||||
let value = serde_yaml::from_str(&text)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
pub fn op_yaml_stringify(#[serde] value: serde_json::Value) -> Result<String, AnyError> {
|
||||
let value = serde_yaml::to_string(&value)?;
|
||||
Ok(value)
|
||||
}
|
||||
@@ -7,23 +7,20 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use base64::Engine;
|
||||
use http::{HeaderMap, HeaderName, HeaderValue, Method};
|
||||
use http::header::{ACCEPT, USER_AGENT};
|
||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
use log::{error, info, warn};
|
||||
use mime_guess::Mime;
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::Method;
|
||||
use reqwest::{multipart, Url};
|
||||
use reqwest::redirect::Policy;
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
use tauri::{Manager, WebviewWindow};
|
||||
use tauri::{Manager, Window};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::watch::Receiver;
|
||||
use tokio::sync::watch::{Receiver};
|
||||
|
||||
use crate::render::variables_from_environment;
|
||||
use crate::{models, render, response_err};
|
||||
|
||||
pub async fn send_http_request(
|
||||
window: &WebviewWindow,
|
||||
window: &Window,
|
||||
request: models::HttpRequest,
|
||||
response: &models::HttpResponse,
|
||||
environment: Option<models::Environment>,
|
||||
@@ -35,11 +32,9 @@ pub async fn send_http_request(
|
||||
let workspace = models::get_workspace(window, &request.workspace_id)
|
||||
.await
|
||||
.expect("Failed to get Workspace");
|
||||
let vars = variables_from_environment(&workspace, environment_ref);
|
||||
|
||||
let mut url_string = render::render(&request.url, &vars);
|
||||
let mut url_string = render::render(&request.url, &workspace, environment.as_ref());
|
||||
|
||||
url_string = ensure_proto(&url_string);
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
url_string = format!("http://{}", url_string);
|
||||
}
|
||||
@@ -49,7 +44,6 @@ pub async fn send_http_request(
|
||||
true => Policy::limited(10), // TODO: Handle redirects natively
|
||||
false => Policy::none(),
|
||||
})
|
||||
.connection_verbose(true)
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
@@ -94,24 +88,14 @@ pub async fn send_http_request(
|
||||
let uri = match http::Uri::from_str(url_string.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
return response_err(
|
||||
response,
|
||||
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
|
||||
window,
|
||||
)
|
||||
.await;
|
||||
return response_err(response, e.to_string(), window).await;
|
||||
}
|
||||
};
|
||||
// Yes, we're parsing both URI and URL because they could return different errors
|
||||
let url = match Url::from_str(uri.to_string().as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
return response_err(
|
||||
response,
|
||||
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
|
||||
window,
|
||||
)
|
||||
.await;
|
||||
return response_err(response, e.to_string(), window).await;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,6 +112,7 @@ pub async fn send_http_request(
|
||||
// everything manually to know that).
|
||||
// if let Some(cookie_store) = maybe_cookie_store.clone() {
|
||||
// let values1 = cookie_store.get_request_values(&url);
|
||||
// println!("COOKIE VLUAES: {:?}", values1.collect::<Vec<_>>());
|
||||
// let raw_value = cookie_store.get_request_values(&url)
|
||||
// .map(|(name, value)| format!("{}={}", name, value))
|
||||
// .collect::<Vec<_>>()
|
||||
@@ -147,8 +132,8 @@ pub async fn send_http_request(
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = render::render(&h.name, &vars);
|
||||
let value = render::render(&h.value, &vars);
|
||||
let name = render::render(&h.name, &workspace, environment_ref);
|
||||
let value = render::render(&h.value, &workspace, environment_ref);
|
||||
|
||||
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
|
||||
Ok(n) => n,
|
||||
@@ -183,8 +168,8 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &vars);
|
||||
let password = render::render(raw_password, &vars);
|
||||
let username = render::render(raw_username, &workspace, environment_ref);
|
||||
let password = render::render(raw_password, &workspace, environment_ref);
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
@@ -194,7 +179,7 @@ pub async fn send_http_request(
|
||||
);
|
||||
} else if b == "bearer" {
|
||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||
let token = render::render(raw_token, &vars);
|
||||
let token = render::render(raw_token, &workspace, environment_ref);
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
||||
@@ -208,8 +193,8 @@ pub async fn send_http_request(
|
||||
continue;
|
||||
}
|
||||
query_params.push((
|
||||
render::render(&p.name, &vars),
|
||||
render::render(&p.value, &vars),
|
||||
render::render(&p.name, &workspace, environment_ref),
|
||||
render::render(&p.value, &workspace, environment_ref),
|
||||
));
|
||||
}
|
||||
request_builder = request_builder.query(&query_params);
|
||||
@@ -225,7 +210,7 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let body = render::render(raw_text, &vars);
|
||||
let body = render::render(raw_text, &workspace, environment_ref);
|
||||
request_builder = request_builder.body(body);
|
||||
} else if body_type == "application/x-www-form-urlencoded"
|
||||
&& request_body.contains_key("form")
|
||||
@@ -252,25 +237,13 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
form_params.push((render::render(name, &vars), render::render(value, &vars)));
|
||||
form_params.push((
|
||||
render::render(name, &workspace, environment_ref),
|
||||
render::render(value, &workspace, environment_ref),
|
||||
));
|
||||
}
|
||||
}
|
||||
request_builder = request_builder.form(&form_params);
|
||||
} else if body_type == "binary" && request_body.contains_key("filePath") {
|
||||
let file_path = request_body
|
||||
.get("filePath")
|
||||
.ok_or("filePath not set")?
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
match fs::read(file_path).map_err(|e| e.to_string()) {
|
||||
Ok(f) => {
|
||||
request_builder = request_builder.body(f);
|
||||
}
|
||||
Err(e) => {
|
||||
return response_err(response, e, window).await;
|
||||
}
|
||||
}
|
||||
} else if body_type == "multipart/form-data" && request_body.contains_key("form") {
|
||||
let mut multipart_form = multipart::Form::new();
|
||||
if let Some(form_definition) = request_body.get("form") {
|
||||
@@ -280,71 +253,38 @@ pub async fn send_http_request(
|
||||
.unwrap_or(empty_bool)
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
let name_raw = p
|
||||
let name = p
|
||||
.get("name")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
if !enabled || name_raw.is_empty() {
|
||||
if !enabled || name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_path = p
|
||||
let file = p
|
||||
.get("file")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
let value_raw = p
|
||||
let value = p
|
||||
.get("value")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
let name = render::render(name_raw, &vars);
|
||||
let mut part = if file_path.is_empty() {
|
||||
multipart::Part::text(render::render(value_raw, &vars))
|
||||
} else {
|
||||
match fs::read(file_path) {
|
||||
Ok(f) => multipart::Part::bytes(f),
|
||||
Err(e) => {
|
||||
return response_err(response, e.to_string(), window).await;
|
||||
multipart_form = multipart_form.part(
|
||||
render::render(name, &workspace, environment_ref),
|
||||
match !file.is_empty() {
|
||||
true => {
|
||||
multipart::Part::bytes(fs::read(file).map_err(|e| e.to_string())?)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ct_raw = p
|
||||
.get("contentType")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Set or guess mimetype
|
||||
if !ct_raw.is_empty() {
|
||||
let content_type = render::render(ct_raw, &vars);
|
||||
part = part
|
||||
.mime_str(content_type.as_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
} else if !file_path.is_empty() {
|
||||
let default_mime = Mime::from_str("application/octet-stream").unwrap();
|
||||
let mime = mime_guess::from_path(file_path).first_or(default_mime);
|
||||
part = part
|
||||
.mime_str(mime.essence_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
// Set fil path if not empty
|
||||
if !file_path.is_empty() {
|
||||
let filename = PathBuf::from(file_path)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
part = part.file_name(filename);
|
||||
}
|
||||
|
||||
multipart_form = multipart_form.part(name, part);
|
||||
false => multipart::Part::text(render::render(
|
||||
value,
|
||||
&workspace,
|
||||
environment_ref,
|
||||
)),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
headers.remove("Content-Type"); // reqwest will add this automatically
|
||||
@@ -367,11 +307,11 @@ pub async fn send_http_request(
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let (resp_tx, resp_rx) = oneshot::channel();
|
||||
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = resp_tx.send(client.execute(sendable_req).await);
|
||||
});
|
||||
|
||||
|
||||
let raw_response = tokio::select! {
|
||||
Ok(r) = resp_rx => {r}
|
||||
_ = cancel_rx.changed() => {
|
||||
@@ -398,11 +338,11 @@ pub async fn send_http_request(
|
||||
response.url = v.url().to_string();
|
||||
response.remote_addr = v.remote_addr().map(|a| a.to_string());
|
||||
response.version = match v.version() {
|
||||
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
http::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
http::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
http::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
http::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
http::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -418,7 +358,7 @@ pub async fn send_http_request(
|
||||
|
||||
{
|
||||
// Write body to FS
|
||||
let dir = window.app_handle().path().app_data_dir().unwrap();
|
||||
let dir = window.app_handle().path_resolver().app_data_dir().unwrap();
|
||||
let base_dir = dir.join("responses");
|
||||
create_dir_all(base_dir.clone()).expect("Failed to create responses dir");
|
||||
let body_path = match response.id.is_empty() {
|
||||
@@ -483,26 +423,3 @@ pub async fn send_http_request(
|
||||
Err(e) => response_err(response, e.to_string(), window).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_proto(url_str: &str) -> String {
|
||||
if url_str.starts_with("http://") || url_str.starts_with("https://") {
|
||||
return url_str.to_string();
|
||||
}
|
||||
|
||||
// Url::from_str will fail without a proto, so add one
|
||||
let parseable_url = format!("http://{}", url_str);
|
||||
if let Ok(u) = Url::from_str(parseable_url.as_str()) {
|
||||
match u.host() {
|
||||
Some(host) => {
|
||||
let h = host.to_string();
|
||||
// These TLDs force HTTPS
|
||||
if h.ends_with(".app") || h.ends_with(".dev") || h.ends_with(".page") {
|
||||
return format!("https://{url_str}");
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
format!("http://{url_str}")
|
||||
}
|
||||
1937
src-tauri/src/lib.rs
1937
src-tauri/src/lib.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,38 +7,9 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlx::types::chrono::NaiveDateTime;
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tauri::{AppHandle, Manager, WebviewWindow, Wry};
|
||||
use tauri::{AppHandle, Manager, Wry};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub enum ModelType {
|
||||
TypeCookieJar,
|
||||
TypeEnvironment,
|
||||
TypeFolder,
|
||||
TypeGrpcConnection,
|
||||
TypeGrpcEvent,
|
||||
TypeGrpcRequest,
|
||||
TypeHttpRequest,
|
||||
TypeHttpResponse,
|
||||
TypeWorkspace,
|
||||
}
|
||||
|
||||
impl ModelType {
|
||||
pub fn id_prefix(&self) -> String {
|
||||
match self {
|
||||
ModelType::TypeCookieJar => "cj",
|
||||
ModelType::TypeEnvironment => "ev",
|
||||
ModelType::TypeFolder => "fl",
|
||||
ModelType::TypeGrpcConnection => "gc",
|
||||
ModelType::TypeGrpcEvent => "ge",
|
||||
ModelType::TypeGrpcRequest => "gr",
|
||||
ModelType::TypeHttpRequest => "rq",
|
||||
ModelType::TypeHttpResponse => "rs",
|
||||
ModelType::TypeWorkspace => "wk",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -52,14 +23,7 @@ pub struct Settings {
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub theme: String,
|
||||
pub appearance: String,
|
||||
pub theme_dark: String,
|
||||
pub theme_light: String,
|
||||
pub update_channel: String,
|
||||
pub interface_font_size: i64,
|
||||
pub interface_scale: i64,
|
||||
pub editor_font_size: i64,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub open_workspace_new_window: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -462,9 +426,9 @@ pub async fn get_workspace(mgr: &impl Manager<Wry>, id: &str) -> Result<Workspac
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_workspace(window: &WebviewWindow, id: &str) -> Result<Workspace, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let workspace = get_workspace(window, id).await?;
|
||||
pub async fn delete_workspace(mgr: &impl Manager<Wry>, id: &str) -> Result<Workspace, sqlx::Error> {
|
||||
let db = get_db(mgr).await;
|
||||
let workspace = get_workspace(mgr, id).await?;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM workspaces
|
||||
@@ -475,11 +439,11 @@ pub async fn delete_workspace(window: &WebviewWindow, id: &str) -> Result<Worksp
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
for r in list_responses_by_workspace_id(window, id).await? {
|
||||
delete_http_response(window, &r.id).await?;
|
||||
for r in list_responses_by_workspace_id(mgr, id).await? {
|
||||
delete_http_response(mgr, &r.id).await?;
|
||||
}
|
||||
|
||||
emit_deleted_model(window, workspace)
|
||||
emit_deleted_model(mgr, workspace)
|
||||
}
|
||||
|
||||
pub async fn get_cookie_jar(mgr: &impl Manager<Wry>, id: &str) -> Result<CookieJar, sqlx::Error> {
|
||||
@@ -517,9 +481,12 @@ pub async fn list_cookie_jars(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_cookie_jar(window: &WebviewWindow, id: &str) -> Result<CookieJar, sqlx::Error> {
|
||||
let cookie_jar = get_cookie_jar(window, id).await?;
|
||||
let db = get_db(window).await;
|
||||
pub async fn delete_cookie_jar(
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<CookieJar, sqlx::Error> {
|
||||
let cookie_jar = get_cookie_jar(mgr, id).await?;
|
||||
let db = get_db(mgr).await;
|
||||
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
@@ -531,25 +498,25 @@ pub async fn delete_cookie_jar(window: &WebviewWindow, id: &str) -> Result<Cooki
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, cookie_jar)
|
||||
emit_deleted_model(mgr, cookie_jar)
|
||||
}
|
||||
|
||||
pub async fn duplicate_grpc_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<GrpcRequest, sqlx::Error> {
|
||||
let mut request = get_grpc_request(window, id).await?.clone();
|
||||
let mut request = get_grpc_request(mgr, id).await?.clone();
|
||||
request.id = "".to_string();
|
||||
upsert_grpc_request(window, &request).await
|
||||
upsert_grpc_request(mgr, &request).await
|
||||
}
|
||||
|
||||
pub async fn upsert_grpc_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
request: &GrpcRequest,
|
||||
) -> Result<GrpcRequest, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let id = match request.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeGrpcRequest),
|
||||
"" => generate_id(Some("gr")),
|
||||
_ => request.id.to_string(),
|
||||
};
|
||||
let trimmed_name = request.name.trim();
|
||||
@@ -562,7 +529,6 @@ pub async fn upsert_grpc_request(
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
workspace_id = excluded.workspace_id,
|
||||
name = excluded.name,
|
||||
folder_id = excluded.folder_id,
|
||||
sort_priority = excluded.sort_priority,
|
||||
@@ -590,8 +556,8 @@ pub async fn upsert_grpc_request(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_grpc_request(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_grpc_request(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -641,12 +607,12 @@ pub async fn list_grpc_requests(
|
||||
}
|
||||
|
||||
pub async fn upsert_grpc_connection(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
connection: &GrpcConnection,
|
||||
) -> Result<GrpcConnection, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let id = match connection.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeGrpcConnection),
|
||||
"" => generate_id(Some("gc")),
|
||||
_ => connection.id.to_string(),
|
||||
};
|
||||
sqlx::query!(
|
||||
@@ -680,8 +646,8 @@ pub async fn upsert_grpc_connection(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_grpc_connection(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_grpc_connection(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -730,12 +696,12 @@ pub async fn list_grpc_connections(
|
||||
}
|
||||
|
||||
pub async fn upsert_grpc_event(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
event: &GrpcEvent,
|
||||
) -> Result<GrpcEvent, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let id = match event.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeGrpcEvent),
|
||||
"" => generate_id(Some("ge")),
|
||||
_ => event.id.to_string(),
|
||||
};
|
||||
sqlx::query!(
|
||||
@@ -766,8 +732,8 @@ pub async fn upsert_grpc_event(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_grpc_event(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_grpc_event(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -812,16 +778,16 @@ pub async fn list_grpc_events(
|
||||
}
|
||||
|
||||
pub async fn upsert_cookie_jar(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
cookie_jar: &CookieJar,
|
||||
) -> Result<CookieJar, sqlx::Error> {
|
||||
let id = match cookie_jar.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeCookieJar),
|
||||
"" => generate_id(Some("cj")),
|
||||
_ => cookie_jar.id.to_string(),
|
||||
};
|
||||
let trimmed_name = cookie_jar.name.trim();
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO cookie_jars (
|
||||
@@ -841,8 +807,8 @@ pub async fn upsert_cookie_jar(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_cookie_jar(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_cookie_jar(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -867,11 +833,11 @@ pub async fn list_environments(
|
||||
}
|
||||
|
||||
pub async fn delete_environment(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<Environment, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let env = get_environment(window, id).await?;
|
||||
let db = get_db(mgr).await;
|
||||
let env = get_environment(mgr, id).await?;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM environments
|
||||
@@ -882,7 +848,7 @@ pub async fn delete_environment(
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, env)
|
||||
emit_deleted_model(mgr, env)
|
||||
}
|
||||
|
||||
async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error> {
|
||||
@@ -891,10 +857,7 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings, sqlx::Error>
|
||||
Settings,
|
||||
r#"
|
||||
SELECT
|
||||
id, model, created_at, updated_at, theme, appearance,
|
||||
theme_dark, theme_light, update_channel,
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
|
||||
open_workspace_new_window
|
||||
id, model, created_at, updated_at, theme, appearance, update_channel
|
||||
FROM settings
|
||||
WHERE id = 'default'
|
||||
"#,
|
||||
@@ -923,48 +886,39 @@ pub async fn get_or_create_settings(mgr: &impl Manager<Wry>) -> Settings {
|
||||
}
|
||||
|
||||
pub async fn update_settings(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
settings: Settings,
|
||||
) -> Result<Settings, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE settings SET (
|
||||
theme, appearance, theme_dark, theme_light, update_channel,
|
||||
interface_font_size, interface_scale, editor_font_size, editor_soft_wrap,
|
||||
open_workspace_new_window
|
||||
) = (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) WHERE id = 'default';
|
||||
theme, appearance, update_channel
|
||||
) = (?, ?, ?) WHERE id = 'default';
|
||||
"#,
|
||||
settings.theme,
|
||||
settings.appearance,
|
||||
settings.theme_dark,
|
||||
settings.theme_light,
|
||||
settings.update_channel,
|
||||
settings.interface_font_size,
|
||||
settings.interface_scale,
|
||||
settings.editor_font_size,
|
||||
settings.editor_soft_wrap,
|
||||
settings.open_workspace_new_window,
|
||||
settings.update_channel
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_settings(window).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_settings(mgr).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upsert_environment(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
environment: Environment,
|
||||
) -> Result<Environment, sqlx::Error> {
|
||||
let id = match environment.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeEnvironment),
|
||||
"" => generate_id(Some("ev")),
|
||||
_ => environment.id.to_string(),
|
||||
};
|
||||
let trimmed_name = environment.name.trim();
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO environments (
|
||||
@@ -984,8 +938,8 @@ pub async fn upsert_environment(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_environment(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_environment(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -1045,9 +999,9 @@ pub async fn list_folders(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_folder(window: &WebviewWindow, id: &str) -> Result<Folder, sqlx::Error> {
|
||||
let folder = get_folder(window, id).await?;
|
||||
let db = get_db(window).await;
|
||||
pub async fn delete_folder(mgr: &impl Manager<Wry>, id: &str) -> Result<Folder, sqlx::Error> {
|
||||
let folder = get_folder(mgr, id).await?;
|
||||
let db = get_db(mgr).await;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM folders
|
||||
@@ -1058,17 +1012,17 @@ pub async fn delete_folder(window: &WebviewWindow, id: &str) -> Result<Folder, s
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, folder)
|
||||
emit_deleted_model(mgr, folder)
|
||||
}
|
||||
|
||||
pub async fn upsert_folder(window: &WebviewWindow, r: Folder) -> Result<Folder, sqlx::Error> {
|
||||
pub async fn upsert_folder(mgr: &impl Manager<Wry>, r: Folder) -> Result<Folder, sqlx::Error> {
|
||||
let id = match r.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeFolder),
|
||||
"" => generate_id(Some("fl")),
|
||||
_ => r.id.to_string(),
|
||||
};
|
||||
let trimmed_name = r.name.trim();
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO folders (
|
||||
@@ -1090,32 +1044,32 @@ pub async fn upsert_folder(window: &WebviewWindow, r: Folder) -> Result<Folder,
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_folder(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_folder(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn duplicate_http_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<HttpRequest, sqlx::Error> {
|
||||
let mut request = get_http_request(window, id).await?.clone();
|
||||
let mut request = get_http_request(mgr, id).await?.clone();
|
||||
request.id = "".to_string();
|
||||
upsert_http_request(window, request).await
|
||||
upsert_http_request(mgr, request).await
|
||||
}
|
||||
|
||||
pub async fn upsert_http_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
r: HttpRequest,
|
||||
) -> Result<HttpRequest, sqlx::Error> {
|
||||
let id = match r.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeHttpRequest),
|
||||
"" => generate_id(Some("rq")),
|
||||
_ => r.id.to_string(),
|
||||
};
|
||||
let trimmed_name = r.name.trim();
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1126,7 +1080,6 @@ pub async fn upsert_http_request(
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
updated_at = CURRENT_TIMESTAMP,
|
||||
workspace_id = excluded.workspace_id,
|
||||
name = excluded.name,
|
||||
folder_id = excluded.folder_id,
|
||||
method = excluded.method,
|
||||
@@ -1156,13 +1109,13 @@ pub async fn upsert_http_request(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_http_request(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_http_request(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_http_requests(
|
||||
pub async fn list_requests(
|
||||
mgr: &impl Manager<Wry>,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<HttpRequest>, sqlx::Error> {
|
||||
@@ -1212,15 +1165,15 @@ pub async fn get_http_request(
|
||||
}
|
||||
|
||||
pub async fn delete_http_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<HttpRequest, sqlx::Error> {
|
||||
let req = get_http_request(window, id).await?;
|
||||
let req = get_http_request(mgr, id).await?;
|
||||
|
||||
// DB deletes will cascade but this will delete the files
|
||||
delete_all_http_responses(window, id).await?;
|
||||
delete_all_http_responses(mgr, id).await?;
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM http_requests
|
||||
@@ -1231,12 +1184,12 @@ pub async fn delete_http_request(
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, req)
|
||||
emit_deleted_model(mgr, req)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_http_response(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
request_id: &str,
|
||||
elapsed: i64,
|
||||
elapsed_headers: i64,
|
||||
@@ -1249,10 +1202,10 @@ pub async fn create_http_response(
|
||||
version: Option<&str>,
|
||||
remote_addr: Option<&str>,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
let req = get_http_request(window, request_id).await?;
|
||||
let id = generate_model_id(ModelType::TypeHttpResponse);
|
||||
let req = get_http_request(mgr, request_id).await?;
|
||||
let id = generate_id(Some("rp"));
|
||||
let headers_json = Json(headers);
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO http_responses (
|
||||
@@ -1278,14 +1231,14 @@ pub async fn create_http_response(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_http_response(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_http_response(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cancel_pending_grpc_connections(app: &AppHandle) -> Result<(), sqlx::Error> {
|
||||
let db = get_db(app).await;
|
||||
pub async fn cancel_pending_grpc_connections(mgr: &impl Manager<Wry>) -> Result<(), sqlx::Error> {
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE grpc_connections
|
||||
@@ -1298,8 +1251,8 @@ pub async fn cancel_pending_grpc_connections(app: &AppHandle) -> Result<(), sqlx
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cancel_pending_responses(app: &AppHandle) -> Result<(), sqlx::Error> {
|
||||
let db = get_db(app).await;
|
||||
pub async fn cancel_pending_responses(mgr: &impl Manager<Wry>) -> Result<(), sqlx::Error> {
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE http_responses
|
||||
@@ -1313,27 +1266,27 @@ pub async fn cancel_pending_responses(app: &AppHandle) -> Result<(), sqlx::Error
|
||||
}
|
||||
|
||||
pub async fn update_response_if_id(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
response: &HttpResponse,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
if response.id.is_empty() {
|
||||
Ok(response.clone())
|
||||
} else {
|
||||
update_response(window, response).await
|
||||
update_response(mgr, response).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upsert_workspace(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
workspace: Workspace,
|
||||
) -> Result<Workspace, sqlx::Error> {
|
||||
let id = match workspace.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeWorkspace),
|
||||
"" => generate_id(Some("wk")),
|
||||
_ => workspace.id.to_string(),
|
||||
};
|
||||
let trimmed_name = workspace.name.trim();
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO workspaces (
|
||||
@@ -1361,17 +1314,17 @@ pub async fn upsert_workspace(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_workspace(window, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_workspace(mgr, &id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_response(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
response: &HttpResponse,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE http_responses SET (
|
||||
@@ -1395,8 +1348,8 @@ pub async fn update_response(
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
match get_http_response(window, &response.id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(window, m)),
|
||||
match get_http_response(mgr, &response.id).await {
|
||||
Ok(m) => Ok(emit_upserted_model(mgr, m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -1474,12 +1427,12 @@ pub async fn list_responses_by_workspace_id(
|
||||
}
|
||||
|
||||
pub async fn delete_grpc_request(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<GrpcRequest, sqlx::Error> {
|
||||
let req = get_grpc_request(window, id).await?;
|
||||
let req = get_grpc_request(mgr, id).await?;
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM grpc_requests
|
||||
@@ -1490,16 +1443,16 @@ pub async fn delete_grpc_request(
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, req)
|
||||
emit_deleted_model(mgr, req)
|
||||
}
|
||||
|
||||
pub async fn delete_grpc_connection(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<GrpcConnection, sqlx::Error> {
|
||||
let resp = get_grpc_connection(window, id).await?;
|
||||
let resp = get_grpc_connection(mgr, id).await?;
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM grpc_connections
|
||||
@@ -1510,14 +1463,14 @@ pub async fn delete_grpc_connection(
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, resp)
|
||||
emit_deleted_model(mgr, resp)
|
||||
}
|
||||
|
||||
pub async fn delete_http_response(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
id: &str,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
let resp = get_http_response(window, id).await?;
|
||||
let resp = get_http_response(mgr, id).await?;
|
||||
|
||||
// Delete the body file if it exists
|
||||
if let Some(p) = resp.body_path.clone() {
|
||||
@@ -1526,7 +1479,7 @@ pub async fn delete_http_response(
|
||||
};
|
||||
}
|
||||
|
||||
let db = get_db(window).await;
|
||||
let db = get_db(mgr).await;
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM http_responses
|
||||
@@ -1537,134 +1490,92 @@ pub async fn delete_http_response(
|
||||
.execute(&db)
|
||||
.await;
|
||||
|
||||
emit_deleted_model(window, resp)
|
||||
emit_deleted_model(mgr, resp)
|
||||
}
|
||||
|
||||
pub async fn delete_all_grpc_connections(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
request_id: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
for r in list_grpc_connections(window, request_id).await? {
|
||||
delete_grpc_connection(window, &r.id).await?;
|
||||
for r in list_grpc_connections(mgr, request_id).await? {
|
||||
delete_grpc_connection(mgr, &r.id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_http_responses(
|
||||
window: &WebviewWindow,
|
||||
mgr: &impl Manager<Wry>,
|
||||
request_id: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
for r in list_responses(window, request_id, None).await? {
|
||||
delete_http_response(window, &r.id).await?;
|
||||
for r in list_responses(mgr, request_id, None).await? {
|
||||
delete_http_response(mgr, &r.id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_model_id(model: ModelType) -> String {
|
||||
let id = generate_id();
|
||||
format!("{}_{}", model.id_prefix(), id)
|
||||
}
|
||||
|
||||
pub fn generate_id() -> String {
|
||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 10)
|
||||
pub fn generate_id(prefix: Option<&str>) -> String {
|
||||
let id = Alphanumeric.sample_string(&mut rand::thread_rng(), 10);
|
||||
match prefix {
|
||||
None => id,
|
||||
Some(p) => format!("{p}_{id}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct WorkspaceExport {
|
||||
pub yaak_version: String,
|
||||
pub yaak_schema: i64,
|
||||
pub timestamp: NaiveDateTime,
|
||||
pub resources: WorkspaceExportResources,
|
||||
pub yaak_version: String,
|
||||
pub yaak_schema: i64,
|
||||
pub timestamp: NaiveDateTime,
|
||||
pub resources: WorkspaceExportResources,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct WorkspaceExportResources {
|
||||
pub workspaces: Vec<Workspace>,
|
||||
pub environments: Vec<Environment>,
|
||||
pub folders: Vec<Folder>,
|
||||
pub http_requests: Vec<HttpRequest>,
|
||||
pub grpc_requests: Vec<GrpcRequest>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct ImportResult {
|
||||
pub resources: WorkspaceExportResources,
|
||||
pub workspaces: Vec<Workspace>,
|
||||
pub environments: Vec<Environment>,
|
||||
pub folders: Vec<Folder>,
|
||||
pub http_requests: Vec<HttpRequest>,
|
||||
pub grpc_requests: Vec<GrpcRequest>,
|
||||
}
|
||||
|
||||
pub async fn get_workspace_export_resources(
|
||||
window: &WebviewWindow,
|
||||
workspace_ids: Vec<&str>,
|
||||
app_handle: &AppHandle,
|
||||
workspace_id: &str,
|
||||
) -> WorkspaceExport {
|
||||
let app_handle = window.app_handle();
|
||||
let mut data = WorkspaceExport {
|
||||
let workspace = get_workspace(app_handle, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get workspace");
|
||||
return WorkspaceExport {
|
||||
yaak_version: app_handle.package_info().version.clone().to_string(),
|
||||
yaak_schema: 2,
|
||||
timestamp: chrono::Utc::now().naive_utc(),
|
||||
resources: WorkspaceExportResources {
|
||||
workspaces: Vec::new(),
|
||||
environments: Vec::new(),
|
||||
folders: Vec::new(),
|
||||
http_requests: Vec::new(),
|
||||
grpc_requests: Vec::new(),
|
||||
},
|
||||
};
|
||||
|
||||
for workspace_id in workspace_ids {
|
||||
data.resources.workspaces.push(
|
||||
get_workspace(window, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get workspace"),
|
||||
);
|
||||
data.resources.environments.append(
|
||||
&mut list_environments(window, workspace_id)
|
||||
workspaces: vec![workspace],
|
||||
environments: list_environments(app_handle, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get environments"),
|
||||
);
|
||||
data.resources.folders.append(
|
||||
&mut list_folders(window, workspace_id)
|
||||
folders: list_folders(app_handle, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get folders"),
|
||||
);
|
||||
data.resources.http_requests.append(
|
||||
&mut list_http_requests(window, workspace_id)
|
||||
http_requests: list_requests(app_handle, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get http requests"),
|
||||
);
|
||||
data.resources.grpc_requests.append(
|
||||
&mut list_grpc_requests(window, workspace_id)
|
||||
.expect("Failed to get requests"),
|
||||
grpc_requests: list_grpc_requests(app_handle, workspace_id)
|
||||
.await
|
||||
.expect("Failed to get grpc requests"),
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
struct ModelPayload<M: Serialize + Clone> {
|
||||
pub model: M,
|
||||
pub window_label: String,
|
||||
}
|
||||
|
||||
fn emit_upserted_model<M: Serialize + Clone>(window: &WebviewWindow, model: M) -> M {
|
||||
let payload = ModelPayload {
|
||||
model: model.clone(),
|
||||
window_label: window.label().to_string(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
window.emit("upserted_model", payload).unwrap();
|
||||
fn emit_upserted_model<S: Serialize + Clone>(mgr: &impl Manager<Wry>, model: S) -> S {
|
||||
mgr.emit_all("upserted_model", model.clone()).unwrap();
|
||||
model
|
||||
}
|
||||
|
||||
fn emit_deleted_model<M: Serialize + Clone, E>(window: &WebviewWindow, model: M) -> Result<M, E> {
|
||||
let payload = ModelPayload {
|
||||
model: model.clone(),
|
||||
window_label: window.label().to_string(),
|
||||
};
|
||||
window.emit("deleted_model", payload).unwrap();
|
||||
fn emit_deleted_model<S: Serialize + Clone, E>(mgr: &impl Manager<Wry>, model: S) -> Result<S, E> {
|
||||
mgr.emit_all("deleted_model", model.clone()).unwrap();
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use log::debug;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use crate::analytics::get_num_launches;
|
||||
use crate::models::{get_key_value_raw, set_key_value_raw};
|
||||
|
||||
// Check for updates every hour
|
||||
const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60;
|
||||
|
||||
const KV_NAMESPACE: &str = "notifications";
|
||||
const KV_KEY: &str = "seen";
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakNotifier {
|
||||
last_check: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct YaakNotification {
|
||||
timestamp: DateTime<Utc>,
|
||||
id: String,
|
||||
message: String,
|
||||
action: Option<YaakNotificationAction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct YaakNotificationAction {
|
||||
label: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl YaakNotifier {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_check: SystemTime::UNIX_EPOCH,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn seen(&mut self, app: &AppHandle, id: &str) -> Result<(), String> {
|
||||
let mut seen = get_kv(app).await?;
|
||||
seen.push(id.to_string());
|
||||
debug!("Marked notification as seen {}", id);
|
||||
let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?;
|
||||
set_key_value_raw(app, KV_NAMESPACE, KV_KEY, seen_json.as_str()).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check(&mut self, app: &AppHandle) -> Result<(), String> {
|
||||
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||
|
||||
if ignore_check {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_check = SystemTime::now();
|
||||
|
||||
let num_launches = get_num_launches(app).await;
|
||||
let info = app.package_info().clone();
|
||||
let req = reqwest::Client::default()
|
||||
.request(Method::GET, "https://notify.yaak.app/notifications")
|
||||
.query(&[
|
||||
("version", info.version.to_string()),
|
||||
("launches", num_launches.to_string()),
|
||||
]);
|
||||
let resp = req.send().await.map_err(|e| e.to_string())?;
|
||||
if resp.status() != 200 {
|
||||
debug!("Skipping notification status code {}", resp.status());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let notification = resp
|
||||
.json::<YaakNotification>()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let age = notification.timestamp.signed_duration_since(Utc::now());
|
||||
let seen = get_kv(app).await?;
|
||||
if seen.contains(¬ification.id) || (age > Duration::days(2)) {
|
||||
debug!("Already seen notification {}", notification.id);
|
||||
return Ok(());
|
||||
}
|
||||
debug!("Got notification {:?}", notification);
|
||||
|
||||
let _ = app.emit("notification", notification.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_kv(app: &AppHandle) -> Result<Vec<String>, String> {
|
||||
match get_key_value_raw(app, "notifications", "seen").await {
|
||||
None => Ok(Vec::new()),
|
||||
Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()),
|
||||
}
|
||||
}
|
||||
156
src-tauri/src/plugin.rs
Normal file
156
src-tauri/src/plugin.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use std::fs;
|
||||
|
||||
use boa_engine::{
|
||||
Context, js_string, JsNativeError, JsValue, Module, module::SimpleModuleLoader,
|
||||
property::Attribute, Source,
|
||||
};
|
||||
use boa_engine::builtins::promise::PromiseState;
|
||||
use boa_engine::module::ModuleLoader;
|
||||
use boa_runtime::Console;
|
||||
use log::{debug, error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tauri::AppHandle;
|
||||
|
||||
use crate::models::{WorkspaceExportResources};
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct FilterResult {
|
||||
pub filtered: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
pub struct ImportResult {
|
||||
pub resources: WorkspaceExportResources,
|
||||
}
|
||||
|
||||
pub async fn run_plugin_filter(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
response_body: &str,
|
||||
filter: &str,
|
||||
) -> Option<FilterResult> {
|
||||
let result_json = run_plugin(
|
||||
app_handle,
|
||||
plugin_name,
|
||||
"pluginHookResponseFilter",
|
||||
&[js_string!(response_body).into(), js_string!(filter).into()],
|
||||
);
|
||||
|
||||
if result_json.is_null() {
|
||||
error!("Plugin {} failed to run", plugin_name);
|
||||
return None;
|
||||
}
|
||||
|
||||
let resources: FilterResult =
|
||||
serde_json::from_value(result_json).expect("failed to parse filter plugin result json");
|
||||
Some(resources)
|
||||
}
|
||||
|
||||
pub async fn run_plugin_import(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
file_path: &str,
|
||||
) -> Option<ImportResult> {
|
||||
let file = fs::read_to_string(file_path)
|
||||
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let result_json = run_plugin(
|
||||
app_handle,
|
||||
plugin_name,
|
||||
"pluginHookImport",
|
||||
&[js_string!(file_contents).into()],
|
||||
);
|
||||
|
||||
if result_json.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let resources: ImportResult =
|
||||
serde_json::from_value(result_json).expect("failed to parse result json");
|
||||
Some(resources)
|
||||
}
|
||||
|
||||
fn run_plugin(
|
||||
app_handle: &AppHandle,
|
||||
plugin_name: &str,
|
||||
entrypoint: &str,
|
||||
js_args: &[JsValue],
|
||||
) -> serde_json::Value {
|
||||
let plugin_dir = app_handle
|
||||
.path_resolver()
|
||||
.resolve_resource("plugins")
|
||||
.expect("failed to resolve plugin directory resource")
|
||||
.join(plugin_name);
|
||||
let plugin_index_file = plugin_dir.join("index.mjs");
|
||||
|
||||
debug!(
|
||||
"Running plugin dir={:?} file={:?}",
|
||||
plugin_dir, plugin_index_file
|
||||
);
|
||||
|
||||
// Module loader for the specific plugin
|
||||
let loader = &SimpleModuleLoader::new(plugin_dir).expect("failed to create module loader");
|
||||
let dyn_loader: &dyn ModuleLoader = loader;
|
||||
|
||||
let context = &mut Context::builder()
|
||||
.module_loader(dyn_loader)
|
||||
.build()
|
||||
.expect("failed to create context");
|
||||
|
||||
add_runtime(context);
|
||||
|
||||
let source = Source::from_filepath(&plugin_index_file).expect("Error opening file");
|
||||
|
||||
// Can also pass a `Some(realm)` if you need to execute the module in another realm.
|
||||
let module = Module::parse(source, None, context).expect("failed to parse module");
|
||||
|
||||
// Insert parsed entrypoint into the module loader
|
||||
loader.insert(plugin_index_file, module.clone());
|
||||
|
||||
let promise_result = module
|
||||
.load_link_evaluate(context)
|
||||
.expect("failed to evaluate module");
|
||||
|
||||
// Very important to push forward the job queue after queueing promises.
|
||||
context.run_jobs();
|
||||
|
||||
// Checking if the final promise didn't return an error.
|
||||
match promise_result.state().expect("failed to get promise state") {
|
||||
PromiseState::Pending => {
|
||||
panic!("Promise was pending");
|
||||
}
|
||||
PromiseState::Fulfilled(v) => {
|
||||
assert_eq!(v, JsValue::undefined())
|
||||
}
|
||||
PromiseState::Rejected(err) => {
|
||||
panic!("Failed to link: {}", err.display());
|
||||
}
|
||||
}
|
||||
|
||||
let namespace = module.namespace(context);
|
||||
|
||||
let result = namespace
|
||||
.get(js_string!(entrypoint), context)
|
||||
.expect("failed to get entrypoint")
|
||||
.as_callable()
|
||||
.cloned()
|
||||
.ok_or_else(|| JsNativeError::typ().with_message("export wasn't a function!"))
|
||||
.expect("Failed to get entrypoint")
|
||||
.call(&JsValue::undefined(), js_args, context)
|
||||
.expect("Failed to call entrypoint");
|
||||
|
||||
match result.is_undefined() {
|
||||
true => json!(null), // to_json doesn't work with undefined (yet)
|
||||
false => result
|
||||
.to_json(context)
|
||||
.expect("failed to convert result to json"),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_runtime(context: &mut Context) {
|
||||
let console = Console::init(context);
|
||||
context
|
||||
.register_global_property(js_string!(Console::NAME), console, Attribute::all())
|
||||
.expect("the console builtin shouldn't exist");
|
||||
}
|
||||
@@ -1,124 +1,32 @@
|
||||
use crate::models::{Environment, Workspace};
|
||||
use std::collections::HashMap;
|
||||
use tauri::regex::Regex;
|
||||
|
||||
use sqlx::types::{Json, JsonValue};
|
||||
|
||||
use crate::models::{
|
||||
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
|
||||
};
|
||||
use templates::parse_and_render;
|
||||
|
||||
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
|
||||
let r = r.clone();
|
||||
let vars = &variables_from_environment(w, e);
|
||||
|
||||
HttpRequest {
|
||||
url: render(r.url.as_str(), vars),
|
||||
url_parameters: Json(
|
||||
r.url_parameters
|
||||
.0
|
||||
.iter()
|
||||
.map(|p| HttpUrlParameter {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
})
|
||||
.collect::<Vec<HttpUrlParameter>>(),
|
||||
),
|
||||
headers: Json(
|
||||
r.headers
|
||||
.0
|
||||
.iter()
|
||||
.map(|p| HttpRequestHeader {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
})
|
||||
.collect::<Vec<HttpRequestHeader>>(),
|
||||
),
|
||||
body: Json(
|
||||
r.body
|
||||
.0
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), JsonValue::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, JsonValue>>(),
|
||||
),
|
||||
authentication: Json(
|
||||
r.authentication
|
||||
.0
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), JsonValue::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, JsonValue>>(),
|
||||
),
|
||||
..r
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recursively_render_variables<'s>(
|
||||
m: &HashMap<String, String>,
|
||||
render_count: usize,
|
||||
) -> HashMap<String, String> {
|
||||
let mut did_render = false;
|
||||
let mut new_map = m.clone();
|
||||
for (k, v) in m.clone() {
|
||||
let rendered = render(v.as_str(), m);
|
||||
if rendered != v {
|
||||
did_render = true
|
||||
}
|
||||
new_map.insert(k, rendered);
|
||||
}
|
||||
|
||||
if did_render && render_count <= 3 {
|
||||
new_map = recursively_render_variables(&new_map, render_count + 1);
|
||||
}
|
||||
|
||||
new_map
|
||||
}
|
||||
|
||||
pub fn variables_from_environment(
|
||||
workspace: &Workspace,
|
||||
environment: Option<&Environment>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut variables = HashMap::new();
|
||||
variables = add_variable_to_map(variables, &workspace.variables.0);
|
||||
|
||||
if let Some(e) = environment {
|
||||
variables = add_variable_to_map(variables, &e.variables.0);
|
||||
}
|
||||
|
||||
recursively_render_variables(&variables, 0)
|
||||
}
|
||||
|
||||
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
|
||||
parse_and_render(template, vars, None)
|
||||
}
|
||||
|
||||
fn add_variable_to_map(
|
||||
m: HashMap<String, String>,
|
||||
variables: &Vec<EnvironmentVariable>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut map = m.clone();
|
||||
for variable in variables {
|
||||
pub fn render(template: &str, workspace: &Workspace, environment: Option<&Environment>) -> String {
|
||||
let mut map = HashMap::new();
|
||||
let workspace_variables = &workspace.variables.0;
|
||||
for variable in workspace_variables {
|
||||
if !variable.enabled || variable.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let name = variable.name.as_str();
|
||||
let value = variable.value.as_str();
|
||||
map.insert(name.into(), value.into());
|
||||
map.insert(variable.name.as_str(), variable.value.as_str());
|
||||
}
|
||||
|
||||
map
|
||||
if let Some(e) = environment {
|
||||
let environment_variables = &e.variables.0;
|
||||
for variable in environment_variables {
|
||||
if !variable.enabled || variable.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
map.insert(variable.name.as_str(), variable.value.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}")
|
||||
.expect("Failed to create regex")
|
||||
.replace_all(template, |caps: &tauri::regex::Captures| {
|
||||
let key = caps.get(1).unwrap().as_str();
|
||||
map.get(key).unwrap_or(&"")
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
use hex_color::HexColor;
|
||||
use log::warn;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime, Window, WindowEvent,
|
||||
};
|
||||
|
||||
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
|
||||
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
|
||||
|
||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||
|
||||
unsafe impl Send for UnsafeWindowHandle {}
|
||||
|
||||
unsafe impl Sync for UnsafeWindowHandle {}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("mac_window")
|
||||
.on_window_ready(|window| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
setup_traffic_light_positioner(&window);
|
||||
let h = window.app_handle();
|
||||
|
||||
let window_for_theme = window.clone();
|
||||
let id1 = h.listen("yaak_bg_changed", move |ev| {
|
||||
let color_str: String = match serde_json::from_str(ev.payload()) {
|
||||
Ok(color) => color,
|
||||
Err(err) => {
|
||||
warn!("Failed to JSON parse color '{}': {}", ev.payload(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match HexColor::parse_rgb(color_str.trim()) {
|
||||
Ok(color) => {
|
||||
update_window_theme(window_for_theme.clone(), color);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to parse background color '{}': {}", color_str, err)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let window_for_title = window.clone();
|
||||
let id2 = h.listen("yaak_title_changed", move |ev| {
|
||||
let title: String = match serde_json::from_str(ev.payload()) {
|
||||
Ok(title) => title,
|
||||
Err(err) => {
|
||||
warn!("Failed to parse window title \"{}\": {}", ev.payload(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
update_window_title(window_for_title.clone(), title);
|
||||
});
|
||||
|
||||
let h = h.clone();
|
||||
window.on_window_event(move |e| {
|
||||
match e {
|
||||
WindowEvent::Destroyed => {
|
||||
h.unlisten(id1);
|
||||
h.unlisten(id2);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
});
|
||||
}
|
||||
return;
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
|
||||
use cocoa::{appkit::NSWindow, base::nil, foundation::NSString};
|
||||
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
|
||||
let window2 = window.clone();
|
||||
let label = window.label().to_string();
|
||||
let _ = window.run_on_main_thread(move || {
|
||||
let win_title = NSString::alloc(nil).init_str(&title);
|
||||
let handle = window_handle;
|
||||
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn update_window_theme<R: Runtime>(window: Window<R>, color: HexColor) {
|
||||
use cocoa::appkit::{
|
||||
NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
|
||||
};
|
||||
|
||||
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
|
||||
let label = window.label().to_string();
|
||||
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
let window2 = window.clone();
|
||||
let _ = window.run_on_main_thread(move || {
|
||||
let handle = window_handle;
|
||||
|
||||
let selected_appearance = if brightness >= 128 {
|
||||
NSAppearance(NSAppearanceNameVibrantLight)
|
||||
} else {
|
||||
NSAppearance(NSAppearanceNameVibrantDark)
|
||||
};
|
||||
|
||||
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64, label: String) {
|
||||
if label.starts_with("nested_") {
|
||||
return;
|
||||
}
|
||||
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
|
||||
let ns_window = ns_window_handle.0 as cocoa::base::id;
|
||||
unsafe {
|
||||
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize =
|
||||
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height;
|
||||
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug)]
|
||||
struct WindowState<R: Runtime> {
|
||||
window: Window<R>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
use cocoa::base::{id, BOOL};
|
||||
use cocoa::delegate;
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use objc::runtime::{Object, Sel};
|
||||
use std::ffi::c_void;
|
||||
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
window.label().to_string(),
|
||||
);
|
||||
|
||||
// Ensure they stay in place while resizing the window.
|
||||
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
|
||||
this: &Object,
|
||||
func: F,
|
||||
) {
|
||||
let ptr = unsafe {
|
||||
let x: *mut c_void = *this.get_ivar("app_box");
|
||||
&mut *(x as *mut WindowState<R>)
|
||||
};
|
||||
func(ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ns_win = window
|
||||
.ns_window()
|
||||
.expect("NS Window should exist to mount traffic light delegate.")
|
||||
as id;
|
||||
|
||||
let current_delegate: id = ns_win.delegate();
|
||||
|
||||
extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, windowShouldClose: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillClose: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resize<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
let id = state
|
||||
.window
|
||||
.ns_window()
|
||||
.expect("NS window should exist on state to handle resize")
|
||||
as id;
|
||||
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
state.window.label().to_string(),
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidResize: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidMove: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_change_backing_properties(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidBecomeKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidResignKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, draggingEntered: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_prepare_for_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, prepareForDragOperation: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, performDragOperation: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, concludeDragOperation: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, draggingExited: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_use_full_screen_presentation_options(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
proposed_options: NSUInteger,
|
||||
) -> NSUInteger {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
|
||||
let id = state.window.ns_window().expect("Failed to emit event") as id;
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
state.window.label().to_string(),
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_fail_to_enter_full_screen(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_change(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![
|
||||
super_del,
|
||||
effectiveAppearanceDidChangedOnMainThread: notification
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Are we de-allocing this properly ? (I miss safe Rust :( )
|
||||
let window_label = window.label().to_string();
|
||||
|
||||
let app_state = WindowState {
|
||||
window: window.clone(),
|
||||
};
|
||||
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||
let random_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(20)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
|
||||
// delegate with the same name.
|
||||
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
|
||||
|
||||
ns_win.setDelegate_(delegate!(&delegate_name, {
|
||||
window: id = ns_win,
|
||||
app_box: *mut c_void = app_box,
|
||||
toolbar: id = cocoa::base::nil,
|
||||
super_delegate: id = current_delegate,
|
||||
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
||||
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
||||
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
||||
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
||||
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
|
||||
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
||||
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
||||
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tauri::api::dialog;
|
||||
use tauri::{updater, AppHandle, Window};
|
||||
|
||||
use crate::is_dev;
|
||||
|
||||
@@ -19,28 +17,6 @@ pub struct YaakUpdater {
|
||||
pub enum UpdateMode {
|
||||
Stable,
|
||||
Beta,
|
||||
Alpha,
|
||||
}
|
||||
|
||||
impl Display for UpdateMode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
UpdateMode::Stable => "stable",
|
||||
UpdateMode::Beta => "beta",
|
||||
UpdateMode::Alpha => "alpha",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateMode {
|
||||
pub fn new(mode: &str) -> UpdateMode {
|
||||
match mode {
|
||||
"beta" => UpdateMode::Beta,
|
||||
"alpha" => UpdateMode::Alpha,
|
||||
_ => UpdateMode::Stable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YaakUpdater {
|
||||
@@ -53,57 +29,64 @@ impl YaakUpdater {
|
||||
&mut self,
|
||||
app_handle: &AppHandle,
|
||||
mode: UpdateMode,
|
||||
) -> Result<bool, tauri_plugin_updater::Error> {
|
||||
) -> Result<bool, updater::Error> {
|
||||
self.last_update_check = SystemTime::now();
|
||||
|
||||
info!("Checking for updates mode={}", mode);
|
||||
let update_mode = get_update_mode_str(mode);
|
||||
let enabled = !is_dev();
|
||||
info!(
|
||||
"Checking for updates mode={} enabled={}",
|
||||
update_mode, enabled
|
||||
);
|
||||
|
||||
let update_check_result = app_handle
|
||||
.updater_builder()
|
||||
.header("X-Update-Mode", mode.to_string())?
|
||||
.build()?
|
||||
if !enabled {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match app_handle
|
||||
.updater()
|
||||
.header("X-Update-Mode", update_mode)?
|
||||
.check()
|
||||
.await;
|
||||
|
||||
match update_check_result {
|
||||
Ok(Some(update)) => {
|
||||
.await
|
||||
{
|
||||
Ok(update) => {
|
||||
let h = app_handle.clone();
|
||||
app_handle
|
||||
.dialog()
|
||||
.message(format!(
|
||||
dialog::ask(
|
||||
None::<&Window>,
|
||||
"Update Available",
|
||||
format!(
|
||||
"{} is available. Would you like to download and install it now?",
|
||||
update.version
|
||||
))
|
||||
.ok_button_label("Download")
|
||||
.cancel_button_label("Later")
|
||||
.title("Update Available")
|
||||
.show(|confirmed| {
|
||||
update.latest_version()
|
||||
),
|
||||
|confirmed| {
|
||||
if !confirmed {
|
||||
return;
|
||||
}
|
||||
tauri::async_runtime::spawn(async move {
|
||||
match update.download_and_install(|_, _| {}, || {}).await {
|
||||
match update.download_and_install().await {
|
||||
Ok(_) => {
|
||||
if h.dialog()
|
||||
.message("Would you like to restart the app?")
|
||||
.title("Update Installed")
|
||||
.ok_button_label("Restart")
|
||||
.cancel_button_label("Later")
|
||||
.blocking_show()
|
||||
{
|
||||
if dialog::blocking::ask(
|
||||
None::<&Window>,
|
||||
"Update Installed",
|
||||
"Would you like to restart the app?",
|
||||
) {
|
||||
h.restart();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
h.dialog()
|
||||
.message(format!("The update failed to install: {}", e));
|
||||
dialog::message(
|
||||
None::<&Window>,
|
||||
"Update Failed",
|
||||
format!("The update failed to install: {}", e),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => Ok(false),
|
||||
Err(updater::Error::UpToDate) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -111,18 +94,27 @@ impl YaakUpdater {
|
||||
&mut self,
|
||||
app_handle: &AppHandle,
|
||||
mode: UpdateMode,
|
||||
) -> Result<bool, tauri_plugin_updater::Error> {
|
||||
) -> Result<bool, updater::Error> {
|
||||
let ignore_check =
|
||||
self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||
if ignore_check {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Don't check if dev
|
||||
if is_dev() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.force_check(app_handle, mode).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_mode_from_str(mode: &str) -> UpdateMode {
|
||||
match mode {
|
||||
"beta" => UpdateMode::Beta,
|
||||
_ => UpdateMode::Stable,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_update_mode_str(mode: UpdateMode) -> &'static str {
|
||||
match mode {
|
||||
UpdateMode::Stable => "stable",
|
||||
UpdateMode::Beta => "beta",
|
||||
}
|
||||
}
|
||||
|
||||
53
src-tauri/src/window_ext.rs
Normal file
53
src-tauri/src/window_ext.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use tauri::{Runtime, Window};
|
||||
|
||||
const TRAFFIC_LIGHT_OFFSET_X: f64 = 13.0;
|
||||
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 18.0;
|
||||
|
||||
pub trait TrafficLightWindowExt {
|
||||
fn position_traffic_lights(&self);
|
||||
}
|
||||
|
||||
impl<R: Runtime> TrafficLightWindowExt for Window<R> {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn position_traffic_lights(&self) {
|
||||
// No-op on other platforms
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(&self) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
let x = TRAFFIC_LIGHT_OFFSET_X;
|
||||
let y = TRAFFIC_LIGHT_OFFSET_Y;
|
||||
|
||||
unsafe {
|
||||
let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize =
|
||||
window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
||||
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user