mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:38:26 +02:00
Compare commits
157 Commits
v2025.6.1
...
v2025.9.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569e506f32 | ||
|
|
6d7a08758f | ||
|
|
20dfd50a7d | ||
|
|
d747eb5e45 | ||
|
|
81fca7c54f | ||
|
|
5465efea84 | ||
|
|
96a3630725 | ||
|
|
f1b6c89186 | ||
|
|
9c52652a5e | ||
|
|
84219571e8 | ||
|
|
7ced183b11 | ||
|
|
593a7ab7e5 | ||
|
|
a4c4663011 | ||
|
|
5745a96106 | ||
|
|
5449e3c620 | ||
|
|
7b6278405c | ||
|
|
8164a61376 | ||
|
|
2e9f21f838 | ||
|
|
0d725b59bd | ||
|
|
632860c29b | ||
|
|
e1cf16f6e1 | ||
|
|
47c9cfb295 | ||
|
|
6389fd3b8f | ||
|
|
d318546d0c | ||
|
|
2f60b7b1f3 | ||
|
|
75dc82570b | ||
|
|
d7a7a64ec4 | ||
|
|
3aae1b52d1 | ||
|
|
9eddf716e1 | ||
|
|
554e632c19 | ||
|
|
054916b7af | ||
|
|
f2a63087b0 | ||
|
|
6f0d4ad5e4 | ||
|
|
cd3530f598 | ||
|
|
53aea914ac | ||
|
|
dc0c1decee | ||
|
|
32d56f2274 | ||
|
|
ef86c1d189 | ||
|
|
e264c50427 | ||
|
|
f05ad62301 | ||
|
|
0a6228bf16 | ||
|
|
fa3a0b57f9 | ||
|
|
4390c02117 | ||
|
|
77011176af | ||
|
|
759fc503d3 | ||
|
|
0cb633e479 | ||
|
|
81ceb981e8 | ||
|
|
4dae1a7955 | ||
|
|
d119f4cab2 | ||
|
|
7e1eb90d29 | ||
|
|
bf97ea1659 | ||
|
|
749ca968ec | ||
|
|
0c54b481fb | ||
|
|
4943bad8ec | ||
|
|
450dbd0053 | ||
|
|
236c8fa656 | ||
|
|
1dfc2ee602 | ||
|
|
1d158082f6 | ||
|
|
f3e44c53d7 | ||
|
|
c8d5e7c97b | ||
|
|
9bde6bbd0a | ||
|
|
df5be218a5 | ||
|
|
2deb870bb6 | ||
|
|
0f9975339c | ||
|
|
6ad4e7bbb5 | ||
|
|
2bcf67aaa6 | ||
|
|
c01b8ce4ca | ||
|
|
f7bb649b16 | ||
|
|
e3e67c8df7 | ||
|
|
c9698c0f23 | ||
|
|
2cdd1d8136 | ||
|
|
8d8e5c0317 | ||
|
|
4e66a73677 | ||
|
|
08f1bc4e65 | ||
|
|
c6d9cb9c9e | ||
|
|
efbb90dd60 | ||
|
|
7a7940d365 | ||
|
|
8a6f80a181 | ||
|
|
e8e0097e2d | ||
|
|
f475b05c51 | ||
|
|
7e5f9004e2 | ||
|
|
660771b48c | ||
|
|
030e8b837e | ||
|
|
a42cba567c | ||
|
|
484b5b2fd8 | ||
|
|
a71fb8ed6c | ||
|
|
5b8114f6f3 | ||
|
|
68637d24c7 | ||
|
|
c097afe657 | ||
|
|
78bc7d7909 | ||
|
|
b68ce44d52 | ||
|
|
632344d166 | ||
|
|
f3814b7d2b | ||
|
|
618a544dbd | ||
|
|
9a55426236 | ||
|
|
b7ad490c9b | ||
|
|
2095cb88c2 | ||
|
|
a9e05ae988 | ||
|
|
99a6c38632 | ||
|
|
b2766509e3 | ||
|
|
3f5b5a397c | ||
|
|
923b1ac830 | ||
|
|
17dbe7c9a7 | ||
|
|
df80cdfe33 | ||
|
|
eb1916b773 | ||
|
|
a3df0489b1 | ||
|
|
b19e036a61 | ||
|
|
b51e37f221 | ||
|
|
cf9882b5b9 | ||
|
|
bbf85c953d | ||
|
|
17ddc76223 | ||
|
|
754ec0ba86 | ||
|
|
1198aa7d87 | ||
|
|
43437abae7 | ||
|
|
9439cfa2ba | ||
|
|
a731ccc8bd | ||
|
|
451c8b9dde | ||
|
|
b7682db9a3 | ||
|
|
7e2d72c4e3 | ||
|
|
28bb460409 | ||
|
|
56d635166b | ||
|
|
f6a7257104 | ||
|
|
1fce060ef7 | ||
|
|
5c966e5a95 | ||
|
|
0520ef5d43 | ||
|
|
25b110778a | ||
|
|
327bf84e57 | ||
|
|
1c48b309b5 | ||
|
|
7c5dec821d | ||
|
|
dcd8f6c08a | ||
|
|
31f9a63c3b | ||
|
|
e902b67a63 | ||
|
|
b11c72fde4 | ||
|
|
07b90c6ae3 | ||
|
|
ba6163b6d8 | ||
|
|
8055b625d0 | ||
|
|
3a61ffbbb0 | ||
|
|
f8478677c5 | ||
|
|
f5094c5a94 | ||
|
|
8300187566 | ||
|
|
cd8ab3616e | ||
|
|
be0c92b755 | ||
|
|
c34ea20406 | ||
|
|
6e9b1db196 | ||
|
|
d83aabd2be | ||
|
|
d46479cd22 | ||
|
|
19cae33382 | ||
|
|
267cd079ad | ||
|
|
19c1efc73e | ||
|
|
dfa9a22861 | ||
|
|
533f9bacc4 | ||
|
|
0358748729 | ||
|
|
1540d0a5a5 | ||
|
|
d177e164f1 | ||
|
|
f1355c9d15 | ||
|
|
485a9ea47c | ||
|
|
dbc606fb53 |
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -54,15 +54,24 @@ jobs:
|
|||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
restore-keys: ${{ runner.os }}-cargo-
|
restore-keys: ${{ runner.os }}-cargo-
|
||||||
|
|
||||||
- name: install dependencies (ubuntu only)
|
- name: install dependencies (Linux only)
|
||||||
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
|
|
||||||
- name: install dependencies (windows only)
|
- name: Install trusted-signing-cli (Windows only)
|
||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
run: cargo install --force trusted-signing-cli
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$dir = "$env:USERPROFILE\trusted-signing"
|
||||||
|
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
||||||
|
$url = "https://github.com/Levminer/trusted-signing-cli/releases/download/0.8.0/trusted-signing-cli.exe"
|
||||||
|
$exe = Join-Path $dir "trusted-signing-cli.exe"
|
||||||
|
Invoke-WebRequest -Uri $url -OutFile $exe
|
||||||
|
echo $dir >> $env:GITHUB_PATH
|
||||||
|
& $exe --version
|
||||||
|
|
||||||
- name: Install NPM Dependencies
|
- name: Install NPM Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
@@ -114,4 +123,4 @@ jobs:
|
|||||||
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
|
releaseBody: '[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)'
|
||||||
releaseDraft: true
|
releaseDraft: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
args: '${{ matrix.args }} --config ./src-tauri/tauri.commercial.conf.json'
|
args: '${{ matrix.args }} --config ./src-tauri/tauri.release.conf.json'
|
||||||
|
|||||||
1
.github/workflows/sponsors.yml
vendored
1
.github/workflows/sponsors.yml
vendored
@@ -40,4 +40,5 @@ jobs:
|
|||||||
uses: JamesIves/github-pages-deploy-action@v4
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
with:
|
with:
|
||||||
branch: main
|
branch: main
|
||||||
|
force: false
|
||||||
folder: '.'
|
folder: '.'
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Rerun the app to apply the migrations.
|
|||||||
|
|
||||||
_Note: For safety, development builds use a separate database location from production builds._
|
_Note: For safety, development builds use a separate database location from production builds._
|
||||||
|
|
||||||
## Lezer Grammer Generation
|
## Lezer Grammar Generation
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Example
|
# Example
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https://github.com/MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a> <a href="https://github.com/dharsanb"><img src="https://github.com/dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a> <a href="https://github.com/railwayapp"><img src="https://github.com/railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a> <a href="https://github.com/caseyamcl"><img src="https://github.com/caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a> <a href="https://github.com/andriyor"><img src="https://github.com/andriyor.png" width="80px" alt="User avatar: andriyor" /></a> <a href="https://github.com/"><img src="https://raw.githubusercontent.com/JamesIves/github-sponsors-readme-action/dev/.github/assets/placeholder.png" width="80px" alt="User avatar: " /></a> <!-- sponsors-premium -->
|
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https://github.com/MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a> <a href="https://github.com/dharsanb"><img src="https://github.com/dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a> <a href="https://github.com/railwayapp"><img src="https://github.com/railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a> <a href="https://github.com/caseyamcl"><img src="https://github.com/caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a> <a href="https://github.com/andriyor"><img src="https://github.com/andriyor.png" width="80px" alt="User avatar: andriyor" /></a> <a href="https://github.com/"><img src="https://raw.githubusercontent.com/JamesIves/github-sponsors-readme-action/dev/.github/assets/placeholder.png" width="80px" alt="User avatar: " /></a> <!-- sponsors-premium -->
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <!-- sponsors-base -->
|
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <a href="https://github.com/majudhu"><img src="https://github.com/majudhu.png" width="50px" alt="User avatar: majudhu" /></a> <a href="https://github.com/axelrindle"><img src="https://github.com/axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a> <a href="https://github.com/jirizverina"><img src="https://github.com/jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="50px" alt="User avatar: chip-well" /></a> <!-- sponsors-base -->
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||

|

|
||||||
@@ -42,7 +42,7 @@ Built with [Tauri](https://tauri.app), Rust, and React, it’s fast, lightweight
|
|||||||
|
|
||||||
### 🔐 Stay secure
|
### 🔐 Stay secure
|
||||||
- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
|
- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
|
||||||
- Secure sensitive values with end-to-end encryption.
|
- Secure sensitive values with encrypted secrets.
|
||||||
- Store secrets in your OS keychain.
|
- Store secrets in your OS keychain.
|
||||||
|
|
||||||
### ☁️ Organize & collaborate
|
### ☁️ Organize & collaborate
|
||||||
@@ -58,7 +58,7 @@ Built with [Tauri](https://tauri.app), Rust, and React, it’s fast, lightweight
|
|||||||
|
|
||||||
## Contribution Policy
|
## Contribution Policy
|
||||||
|
|
||||||
Yaak is open source, but only accepting contributions for bug fixes. To get started,
|
Yaak is open source but only accepting contributions for bug fixes. To get started,
|
||||||
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
|
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
|
||||||
|
|
||||||
## Useful Resources
|
## Useful Resources
|
||||||
@@ -68,4 +68,3 @@ visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment
|
|||||||
- [Yaak vs Postman](https://yaak.app/alternatives/postman)
|
- [Yaak vs Postman](https://yaak.app/alternatives/postman)
|
||||||
- [Yaak vs Bruno](https://yaak.app/alternatives/bruno)
|
- [Yaak vs Bruno](https://yaak.app/alternatives/bruno)
|
||||||
- [Yaak vs Insomnia](https://yaak.app/alternatives/insomnia)
|
- [Yaak vs Insomnia](https://yaak.app/alternatives/insomnia)
|
||||||
|
|
||||||
|
|||||||
672
package-lock.json
generated
672
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -7,40 +7,46 @@
|
|||||||
"url": "git+https://github.com/mountain-loop/yaak.git"
|
"url": "git+https://github.com/mountain-loop/yaak.git"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
"packages/common-lib",
|
||||||
"packages/plugin-runtime",
|
"packages/plugin-runtime",
|
||||||
"packages/plugin-runtime-types",
|
"packages/plugin-runtime-types",
|
||||||
"packages/common-lib",
|
"plugins/action-copy-curl",
|
||||||
|
"plugins/action-copy-grpcurl",
|
||||||
"plugins/auth-apikey",
|
"plugins/auth-apikey",
|
||||||
|
"plugins/auth-aws",
|
||||||
"plugins/auth-basic",
|
"plugins/auth-basic",
|
||||||
"plugins/auth-bearer",
|
"plugins/auth-bearer",
|
||||||
"plugins/auth-jwt",
|
"plugins/auth-jwt",
|
||||||
|
"plugins/auth-ntlm",
|
||||||
"plugins/auth-oauth2",
|
"plugins/auth-oauth2",
|
||||||
"plugins/action-copy-curl",
|
"plugins/auth-oauth1",
|
||||||
"plugins/action-copy-grpcurl",
|
|
||||||
"plugins/filter-jsonpath",
|
"plugins/filter-jsonpath",
|
||||||
"plugins/filter-xpath",
|
"plugins/filter-xpath",
|
||||||
"plugins/importer-curl",
|
"plugins/importer-curl",
|
||||||
"plugins/importer-insomnia",
|
"plugins/importer-insomnia",
|
||||||
"plugins/importer-openapi",
|
"plugins/importer-openapi",
|
||||||
"plugins/importer-postman",
|
"plugins/importer-postman",
|
||||||
|
"plugins/importer-postman-environment",
|
||||||
"plugins/importer-yaak",
|
"plugins/importer-yaak",
|
||||||
"plugins/template-function-cookie",
|
"plugins/template-function-cookie",
|
||||||
"plugins/template-function-timestamp",
|
"plugins/template-function-ctx",
|
||||||
"plugins/template-function-encode",
|
"plugins/template-function-encode",
|
||||||
"plugins/template-function-fs",
|
"plugins/template-function-fs",
|
||||||
"plugins/template-function-hash",
|
"plugins/template-function-hash",
|
||||||
"plugins/template-function-json",
|
"plugins/template-function-json",
|
||||||
"plugins/template-function-prompt",
|
"plugins/template-function-prompt",
|
||||||
|
"plugins/template-function-random",
|
||||||
"plugins/template-function-regex",
|
"plugins/template-function-regex",
|
||||||
"plugins/template-function-request",
|
"plugins/template-function-request",
|
||||||
"plugins/template-function-response",
|
"plugins/template-function-timestamp",
|
||||||
"plugins/template-function-uuid",
|
"plugins/template-function-uuid",
|
||||||
"plugins/template-function-xml",
|
"plugins/template-function-xml",
|
||||||
|
"plugins/template-function-response",
|
||||||
"plugins/themes-yaak",
|
"plugins/themes-yaak",
|
||||||
"src-tauri",
|
"src-tauri",
|
||||||
"src-tauri/yaak-crypto",
|
"src-tauri/yaak-crypto",
|
||||||
"src-tauri/yaak-git",
|
|
||||||
"src-tauri/yaak-fonts",
|
"src-tauri/yaak-fonts",
|
||||||
|
"src-tauri/yaak-git",
|
||||||
"src-tauri/yaak-license",
|
"src-tauri/yaak-license",
|
||||||
"src-tauri/yaak-mac-window",
|
"src-tauri/yaak-mac-window",
|
||||||
"src-tauri/yaak-models",
|
"src-tauri/yaak-models",
|
||||||
@@ -60,9 +66,10 @@
|
|||||||
"build-plugins": "npm run --workspaces --if-present build",
|
"build-plugins": "npm run --workspaces --if-present build",
|
||||||
"test": "npm run --workspaces --if-present test",
|
"test": "npm run --workspaces --if-present test",
|
||||||
"icons": "run-p icons:*",
|
"icons": "run-p icons:*",
|
||||||
"icons:dev": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
|
"icons:dev": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
|
||||||
"icons:release": "tauri icon src-tauri/icons/icon-dev.png --output src-tauri/icons/dev",
|
"icons:release": "tauri icon src-tauri/icons/icon.png --output src-tauri/icons/release",
|
||||||
"bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
|
"bootstrap": "run-s bootstrap:*",
|
||||||
|
"bootstrap:build": "npm run build",
|
||||||
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
|
"bootstrap:vendor-node": "node scripts/vendor-node.cjs",
|
||||||
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
|
||||||
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",
|
||||||
@@ -79,7 +86,7 @@
|
|||||||
"@eslint/compat": "^1.3.0",
|
"@eslint/compat": "^1.3.0",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.29.0",
|
"@eslint/js": "^9.29.0",
|
||||||
"@tauri-apps/cli": "^2.8.4",
|
"@tauri-apps/cli": "^2.9.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||||
"@typescript-eslint/parser": "^8.27.0",
|
"@typescript-eslint/parser": "^8.27.0",
|
||||||
"@yaakapp/cli": "^0.2.7",
|
"@yaakapp/cli": "^0.2.7",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@yaakapp/api",
|
"name": "@yaakapp/api",
|
||||||
"version": "0.6.6",
|
"version": "0.7.1",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"api-client",
|
"api-client",
|
||||||
"insomnia-alternative",
|
"insomnia-alternative",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
|||||||
|
|
||||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||||
|
|
||||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, };
|
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonPrimitive }, };
|
||||||
|
|
||||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
|||||||
|
|
||||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||||
|
|
||||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
|
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "h_stack" } & FormInputHStack | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
|
||||||
|
|
||||||
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
|
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
|
||||||
|
|
||||||
@@ -224,6 +224,8 @@ defaultValue?: string, disabled?: boolean,
|
|||||||
*/
|
*/
|
||||||
description?: string, };
|
description?: string, };
|
||||||
|
|
||||||
|
export type FormInputHStack = { inputs?: Array<FormInput>, hidden?: boolean, };
|
||||||
|
|
||||||
export type FormInputHttpRequest = {
|
export type FormInputHttpRequest = {
|
||||||
/**
|
/**
|
||||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||||
@@ -361,7 +363,11 @@ export type GetKeyValueRequest = { key: string, };
|
|||||||
|
|
||||||
export type GetKeyValueResponse = { value?: string, };
|
export type GetKeyValueResponse = { value?: string, };
|
||||||
|
|
||||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
export type GetTemplateFunctionConfigRequest = { contextId: string, name: string, values: { [key in string]?: JsonPrimitive }, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionConfigResponse = { function: TemplateFunction, pluginRefId: string, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionSummaryResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||||
|
|
||||||
export type GetThemesRequest = Record<string, never>;
|
export type GetThemesRequest = Record<string, never>;
|
||||||
|
|
||||||
@@ -383,9 +389,9 @@ export type ImportResources = { workspaces: Array<Workspace>, environments: Arra
|
|||||||
|
|
||||||
export type ImportResponse = { resources: ImportResources, };
|
export type ImportResponse = { resources: ImportResources, };
|
||||||
|
|
||||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, context: PluginContext, payload: InternalEventPayload, };
|
||||||
|
|
||||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "window_info_request" } & WindowInfoRequest | { "type": "window_info_response" } & WindowInfoResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||||
|
|
||||||
export type JsonPrimitive = string | number | boolean | null;
|
export type JsonPrimitive = string | number | boolean | null;
|
||||||
|
|
||||||
@@ -399,13 +405,13 @@ export type OpenWindowRequest = { url: string,
|
|||||||
*/
|
*/
|
||||||
label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
|
label: string, title?: string, size?: WindowSize, dataDirKey?: string, };
|
||||||
|
|
||||||
export type PluginWindowContext = { "type": "none" } | { "type": "label", label: string, workspace_id: string | null, };
|
export type PluginContext = { id: string, label: string | null, workspaceId: string | null, };
|
||||||
|
|
||||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||||
/**
|
/**
|
||||||
* Text to add to the confirmation button
|
* Text to add to the confirmation button
|
||||||
*/
|
*/
|
||||||
confirmText?: string,
|
confirmText?: string, password?: boolean,
|
||||||
/**
|
/**
|
||||||
* Text to add to the cancel button
|
* Text to add to the cancel button
|
||||||
*/
|
*/
|
||||||
@@ -437,9 +443,9 @@ export type SetKeyValueRequest = { key: string, value: string, };
|
|||||||
|
|
||||||
export type SetKeyValueResponse = {};
|
export type SetKeyValueResponse = {};
|
||||||
|
|
||||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
|
||||||
|
|
||||||
export type TemplateFunction = { name: string, description?: string,
|
export type TemplateFunction = { name: string, previewType?: TemplateFunctionPreviewType, description?: string,
|
||||||
/**
|
/**
|
||||||
* Also support alternative names. This is useful for not breaking existing
|
* Also support alternative names. This is useful for not breaking existing
|
||||||
* tags when changing the `name` property
|
* tags when changing the `name` property
|
||||||
@@ -451,6 +457,8 @@ aliases?: Array<string>, args: Array<TemplateFunctionArg>, };
|
|||||||
*/
|
*/
|
||||||
export type TemplateFunctionArg = FormInput;
|
export type TemplateFunctionArg = FormInput;
|
||||||
|
|
||||||
|
export type TemplateFunctionPreviewType = "live" | "click" | "none";
|
||||||
|
|
||||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||||
|
|
||||||
export type TemplateRenderResponse = { data: JsonValue, };
|
export type TemplateRenderResponse = { data: JsonValue, };
|
||||||
@@ -481,6 +489,10 @@ export type ThemeComponentColors = { surface?: string, surfaceHighlight?: string
|
|||||||
|
|
||||||
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
|
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
|
||||||
|
|
||||||
|
export type WindowInfoRequest = { label: string, };
|
||||||
|
|
||||||
|
export type WindowInfoResponse = { requestId: string | null, environmentId: string | null, workspaceId: string | null, label: string, };
|
||||||
|
|
||||||
export type WindowNavigateEvent = { url: string, };
|
export type WindowNavigateEvent = { url: string, };
|
||||||
|
|
||||||
export type WindowSize = { width: number, height: number, };
|
export type WindowSize = { width: number, height: number, };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,37 @@ import {
|
|||||||
CallHttpAuthenticationRequest,
|
CallHttpAuthenticationRequest,
|
||||||
CallHttpAuthenticationResponse,
|
CallHttpAuthenticationResponse,
|
||||||
FormInput,
|
FormInput,
|
||||||
GetHttpAuthenticationConfigRequest,
|
|
||||||
GetHttpAuthenticationSummaryResponse,
|
GetHttpAuthenticationSummaryResponse,
|
||||||
HttpAuthenticationAction,
|
HttpAuthenticationAction,
|
||||||
} from '../bindings/gen_events';
|
} from '../bindings/gen_events';
|
||||||
import { MaybePromise } from '../helpers';
|
import { MaybePromise } from '../helpers';
|
||||||
import { Context } from './Context';
|
import { Context } from './Context';
|
||||||
|
|
||||||
type DynamicFormInput = FormInput & {
|
type AddDynamicMethod<T> = {
|
||||||
dynamic(
|
dynamic?: (
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
args: GetHttpAuthenticationConfigRequest,
|
args: CallHttpAuthenticationActionArgs,
|
||||||
): MaybePromise<Partial<FormInput> | undefined | null>;
|
) => MaybePromise<Partial<T> | null | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AddDynamic<T> = T extends any
|
||||||
|
? T extends { inputs?: FormInput[] }
|
||||||
|
? Omit<T, 'inputs'> & {
|
||||||
|
inputs: Array<AddDynamic<FormInput>>;
|
||||||
|
dynamic?: (
|
||||||
|
ctx: Context,
|
||||||
|
args: CallHttpAuthenticationActionArgs,
|
||||||
|
) => MaybePromise<
|
||||||
|
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
: T & AddDynamicMethod<T>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type DynamicAuthenticationArg = AddDynamic<FormInput>;
|
||||||
|
|
||||||
export type AuthenticationPlugin = GetHttpAuthenticationSummaryResponse & {
|
export type AuthenticationPlugin = GetHttpAuthenticationSummaryResponse & {
|
||||||
args: (FormInput | DynamicFormInput)[];
|
args: DynamicAuthenticationArg[];
|
||||||
onApply(
|
onApply(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
args: CallHttpAuthenticationRequest,
|
args: CallHttpAuthenticationRequest,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import type {
|
|||||||
ShowToastRequest,
|
ShowToastRequest,
|
||||||
TemplateRenderRequest,
|
TemplateRenderRequest,
|
||||||
} from '../bindings/gen_events.ts';
|
} from '../bindings/gen_events.ts';
|
||||||
import { JsonValue } from '../bindings/serde_json/JsonValue';
|
import type { JsonValue } from '../bindings/serde_json/JsonValue';
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
@@ -36,6 +36,9 @@ export interface Context {
|
|||||||
delete(key: string): Promise<boolean>;
|
delete(key: string): Promise<boolean>;
|
||||||
};
|
};
|
||||||
window: {
|
window: {
|
||||||
|
requestId(): Promise<string | null>;
|
||||||
|
workspaceId(): Promise<string | null>;
|
||||||
|
environmentId(): Promise<string | null>;
|
||||||
openUrl(
|
openUrl(
|
||||||
args: OpenWindowRequest & {
|
args: OpenWindowRequest & {
|
||||||
onNavigate?: (args: { url: string }) => void;
|
onNavigate?: (args: { url: string }) => void;
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import {
|
import { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
|
||||||
CallTemplateFunctionArgs,
|
import { MaybePromise } from '../helpers';
|
||||||
TemplateFunction,
|
import { Context } from './Context';
|
||||||
} from "../bindings/gen_events";
|
|
||||||
import { Context } from "./Context";
|
|
||||||
|
|
||||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
type AddDynamicMethod<T> = {
|
||||||
onRender(
|
dynamic?: (
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
args: CallTemplateFunctionArgs,
|
args: CallTemplateFunctionArgs,
|
||||||
): Promise<string | null>;
|
) => MaybePromise<Partial<T> | null | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddDynamic<T> = T extends any
|
||||||
|
? T extends { inputs?: FormInput[] }
|
||||||
|
? Omit<T, 'inputs'> & {
|
||||||
|
inputs: Array<AddDynamic<FormInput>>;
|
||||||
|
dynamic?: (
|
||||||
|
ctx: Context,
|
||||||
|
args: CallTemplateFunctionArgs,
|
||||||
|
) => MaybePromise<
|
||||||
|
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
: T & AddDynamicMethod<T>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type DynamicTemplateFunctionArg = AddDynamic<FormInput>;
|
||||||
|
|
||||||
|
export type TemplateFunctionPlugin = Omit<TemplateFunction, 'args'> & {
|
||||||
|
args: DynamicTemplateFunctionArg[];
|
||||||
|
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { AuthenticationPlugin } from './AuthenticationPlugin';
|
import { AuthenticationPlugin } from './AuthenticationPlugin';
|
||||||
|
|
||||||
|
import type { Context } from './Context';
|
||||||
import type { FilterPlugin } from './FilterPlugin';
|
import type { FilterPlugin } from './FilterPlugin';
|
||||||
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
||||||
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||||
@@ -6,9 +8,10 @@ import type { ImporterPlugin } from './ImporterPlugin';
|
|||||||
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
||||||
import type { ThemePlugin } from './ThemePlugin';
|
import type { ThemePlugin } from './ThemePlugin';
|
||||||
|
|
||||||
import type { Context } from './Context';
|
|
||||||
|
|
||||||
export type { Context };
|
export type { Context };
|
||||||
|
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
|
||||||
|
export type { DynamicAuthenticationArg } from './AuthenticationPlugin';
|
||||||
|
export type { TemplateFunctionPlugin };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The global structure of a Yaak plugin
|
* The global structure of a Yaak plugin
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PluginContext } from '@yaakapp-internal/plugins';
|
||||||
import type { BootRequest, InternalEvent } from '@yaakapp/api';
|
import type { BootRequest, InternalEvent } from '@yaakapp/api';
|
||||||
import type { EventChannel } from './EventChannel';
|
import type { EventChannel } from './EventChannel';
|
||||||
import { PluginInstance, PluginWorkerData } from './PluginInstance';
|
import { PluginInstance, PluginWorkerData } from './PluginInstance';
|
||||||
@@ -6,14 +7,12 @@ export class PluginHandle {
|
|||||||
#instance: PluginInstance;
|
#instance: PluginInstance;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly pluginRefId: string,
|
pluginRefId: string,
|
||||||
readonly bootRequest: BootRequest,
|
context: PluginContext,
|
||||||
readonly pluginToAppEvents: EventChannel,
|
bootRequest: BootRequest,
|
||||||
|
pluginToAppEvents: EventChannel,
|
||||||
) {
|
) {
|
||||||
const workerData: PluginWorkerData = {
|
const workerData: PluginWorkerData = { pluginRefId, context, bootRequest };
|
||||||
pluginRefId: this.pluginRefId,
|
|
||||||
bootRequest: this.bootRequest,
|
|
||||||
};
|
|
||||||
this.#instance = new PluginInstance(workerData, pluginToAppEvents);
|
this.#instance = new PluginInstance(workerData, pluginToAppEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
BootRequest,
|
BootRequest,
|
||||||
DeleteKeyValueResponse,
|
DeleteKeyValueResponse,
|
||||||
FindHttpResponsesResponse,
|
FindHttpResponsesResponse,
|
||||||
FormInput,
|
|
||||||
GetCookieValueRequest,
|
GetCookieValueRequest,
|
||||||
GetCookieValueResponse,
|
GetCookieValueResponse,
|
||||||
GetHttpRequestByIdResponse,
|
GetHttpRequestByIdResponse,
|
||||||
@@ -13,32 +12,32 @@ import {
|
|||||||
InternalEvent,
|
InternalEvent,
|
||||||
InternalEventPayload,
|
InternalEventPayload,
|
||||||
ListCookieNamesResponse,
|
ListCookieNamesResponse,
|
||||||
PluginWindowContext,
|
PluginContext,
|
||||||
PromptTextResponse,
|
PromptTextResponse,
|
||||||
RenderGrpcRequestResponse,
|
RenderGrpcRequestResponse,
|
||||||
RenderHttpRequestResponse,
|
RenderHttpRequestResponse,
|
||||||
SendHttpRequestResponse,
|
SendHttpRequestResponse,
|
||||||
TemplateFunction,
|
TemplateFunction,
|
||||||
TemplateFunctionArg,
|
|
||||||
TemplateRenderResponse,
|
TemplateRenderResponse,
|
||||||
|
WindowInfoResponse,
|
||||||
} from '@yaakapp-internal/plugins';
|
} from '@yaakapp-internal/plugins';
|
||||||
import { Context, PluginDefinition } from '@yaakapp/api';
|
import { Context, PluginDefinition } from '@yaakapp/api';
|
||||||
import { JsonValue } from '@yaakapp/api/lib/bindings/serde_json/JsonValue';
|
|
||||||
import console from 'node:console';
|
import console from 'node:console';
|
||||||
import { readFileSync, type Stats, statSync, watch } from 'node:fs';
|
import { type Stats, statSync, watch } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { applyDynamicFormInput, applyFormInputDefaults } from './common';
|
||||||
import { EventChannel } from './EventChannel';
|
import { EventChannel } from './EventChannel';
|
||||||
import { migrateTemplateFunctionSelectOptions } from './migrations';
|
import { migrateTemplateFunctionSelectOptions } from './migrations';
|
||||||
|
|
||||||
export interface PluginWorkerData {
|
export interface PluginWorkerData {
|
||||||
bootRequest: BootRequest;
|
bootRequest: BootRequest;
|
||||||
pluginRefId: string;
|
pluginRefId: string;
|
||||||
|
context: PluginContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PluginInstance {
|
export class PluginInstance {
|
||||||
#workerData: PluginWorkerData;
|
#workerData: PluginWorkerData;
|
||||||
#mod: PluginDefinition;
|
#mod: PluginDefinition;
|
||||||
#pkg: { name?: string; version?: string };
|
|
||||||
#pluginToAppEvents: EventChannel;
|
#pluginToAppEvents: EventChannel;
|
||||||
#appToPluginEvents: EventChannel;
|
#appToPluginEvents: EventChannel;
|
||||||
|
|
||||||
@@ -52,18 +51,14 @@ export class PluginInstance {
|
|||||||
await this.#onMessage(event);
|
await this.#onMessage(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reload plugin if the JS or package.json changes
|
|
||||||
const windowContextNone: PluginWindowContext = { type: 'none' };
|
|
||||||
|
|
||||||
this.#mod = {} as any;
|
this.#mod = {} as any;
|
||||||
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8'));
|
|
||||||
|
|
||||||
const fileChangeCallback = async () => {
|
const fileChangeCallback = async () => {
|
||||||
await this.#mod?.dispose?.();
|
await this.#mod?.dispose?.();
|
||||||
this.#importModule();
|
this.#importModule();
|
||||||
await this.#mod?.init?.(this.#newCtx({ type: 'none' }));
|
await this.#mod?.init?.(this.#newCtx(workerData.context));
|
||||||
return this.#sendPayload(
|
return this.#sendPayload(
|
||||||
windowContextNone,
|
workerData.context,
|
||||||
{
|
{
|
||||||
type: 'reload_response',
|
type: 'reload_response',
|
||||||
silent: false,
|
silent: false,
|
||||||
@@ -90,14 +85,14 @@ export class PluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #onMessage(event: InternalEvent) {
|
async #onMessage(event: InternalEvent) {
|
||||||
const ctx = this.#newCtx(event.windowContext);
|
const ctx = this.#newCtx(event.context);
|
||||||
|
|
||||||
const { windowContext, payload, id: replyId } = event;
|
const { context, payload, id: replyId } = event;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (payload.type === 'boot_request') {
|
if (payload.type === 'boot_request') {
|
||||||
await this.#mod?.init?.(ctx);
|
await this.#mod?.init?.(ctx);
|
||||||
this.#sendPayload(windowContext, { type: 'boot_response' }, replyId);
|
this.#sendPayload(context, { type: 'boot_response' }, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +101,7 @@ export class PluginInstance {
|
|||||||
type: 'terminate_response',
|
type: 'terminate_response',
|
||||||
};
|
};
|
||||||
await this.terminate();
|
await this.terminate();
|
||||||
this.#sendPayload(windowContext, payload, replyId);
|
this.#sendPayload(context, payload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +118,10 @@ export class PluginInstance {
|
|||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
resources: reply.resources as any,
|
resources: reply.resources as any,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Continue, to send back an empty reply
|
// Send back an empty reply (below)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +131,7 @@ export class PluginInstance {
|
|||||||
payload: payload.content,
|
payload: payload.content,
|
||||||
mimeType: payload.type,
|
mimeType: payload.type,
|
||||||
});
|
});
|
||||||
this.#sendPayload(windowContext, { type: 'filter_response', ...reply }, replyId);
|
this.#sendPayload(context, { type: 'filter_response', ...reply }, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +149,7 @@ export class PluginInstance {
|
|||||||
pluginRefId: this.#workerData.pluginRefId,
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
actions: reply,
|
actions: reply,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +167,7 @@ export class PluginInstance {
|
|||||||
pluginRefId: this.#workerData.pluginRefId,
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
actions: reply,
|
actions: reply,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,55 +176,74 @@ export class PluginInstance {
|
|||||||
type: 'get_themes_response',
|
type: 'get_themes_response',
|
||||||
themes: this.#mod.themes,
|
themes: this.#mod.themes,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
payload.type === 'get_template_functions_request' &&
|
payload.type === 'get_template_function_summary_request' &&
|
||||||
Array.isArray(this.#mod?.templateFunctions)
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
) {
|
) {
|
||||||
const reply: TemplateFunction[] = this.#mod.templateFunctions.map((templateFunction) => {
|
const functions: TemplateFunction[] = this.#mod.templateFunctions.map(
|
||||||
return {
|
(templateFunction) => {
|
||||||
...migrateTemplateFunctionSelectOptions(templateFunction),
|
return {
|
||||||
// Add everything except render
|
...migrateTemplateFunctionSelectOptions(templateFunction),
|
||||||
onRender: undefined,
|
// Add everything except render
|
||||||
};
|
onRender: undefined,
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
type: 'get_template_functions_response',
|
type: 'get_template_function_summary_response',
|
||||||
pluginRefId: this.#workerData.pluginRefId,
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
functions: reply,
|
functions,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
payload.type === 'get_template_function_config_request' &&
|
||||||
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
|
) {
|
||||||
|
let templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
|
||||||
|
if (templateFunction == null) {
|
||||||
|
this.#sendEmpty(context, replyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn = {
|
||||||
|
...migrateTemplateFunctionSelectOptions(templateFunction),
|
||||||
|
onRender: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
payload.values = applyFormInputDefaults(fn.args, payload.values);
|
||||||
|
const p = { ...payload, purpose: 'preview' } as const;
|
||||||
|
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, p);
|
||||||
|
|
||||||
|
const replyPayload: InternalEventPayload = {
|
||||||
|
type: 'get_template_function_config_response',
|
||||||
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
|
function: { ...fn, args: resolvedArgs },
|
||||||
|
};
|
||||||
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === 'get_http_authentication_summary_request' && this.#mod?.authentication) {
|
if (payload.type === 'get_http_authentication_summary_request' && this.#mod?.authentication) {
|
||||||
const { name, shortLabel, label } = this.#mod.authentication;
|
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
type: 'get_http_authentication_summary_response',
|
type: 'get_http_authentication_summary_response',
|
||||||
name,
|
...this.#mod.authentication,
|
||||||
label,
|
|
||||||
shortLabel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === 'get_http_authentication_config_request' && this.#mod?.authentication) {
|
if (payload.type === 'get_http_authentication_config_request' && this.#mod?.authentication) {
|
||||||
const { args, actions } = this.#mod.authentication;
|
const { args, actions } = this.#mod.authentication;
|
||||||
const resolvedArgs: FormInput[] = [];
|
payload.values = applyFormInputDefaults(args, payload.values);
|
||||||
for (const v of args) {
|
const resolvedArgs = await applyDynamicFormInput(ctx, args, payload);
|
||||||
if (v && 'dynamic' in v) {
|
|
||||||
const dynamicAttrs = await v.dynamic(ctx, payload);
|
|
||||||
const { dynamic, ...other } = v;
|
|
||||||
resolvedArgs.push({ ...other, ...dynamicAttrs } as FormInput);
|
|
||||||
} else if (v) {
|
|
||||||
resolvedArgs.push(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const resolvedActions: HttpAuthenticationAction[] = [];
|
const resolvedActions: HttpAuthenticationAction[] = [];
|
||||||
for (const { onSelect, ...action } of actions ?? []) {
|
for (const { onSelect, ...action } of actions ?? []) {
|
||||||
resolvedActions.push(action);
|
resolvedActions.push(action);
|
||||||
@@ -242,16 +256,17 @@ export class PluginInstance {
|
|||||||
pluginRefId: this.#workerData.pluginRefId,
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === 'call_http_authentication_request' && this.#mod?.authentication) {
|
if (payload.type === 'call_http_authentication_request' && this.#mod?.authentication) {
|
||||||
const auth = this.#mod.authentication;
|
const auth = this.#mod.authentication;
|
||||||
if (typeof auth?.onApply === 'function') {
|
if (typeof auth?.onApply === 'function') {
|
||||||
applyFormInputDefaults(auth.args, payload.values);
|
auth.args = await applyDynamicFormInput(ctx, auth.args, payload);
|
||||||
|
payload.values = applyFormInputDefaults(auth.args, payload.values);
|
||||||
this.#sendPayload(
|
this.#sendPayload(
|
||||||
windowContext,
|
context,
|
||||||
{
|
{
|
||||||
type: 'call_http_authentication_response',
|
type: 'call_http_authentication_response',
|
||||||
...(await auth.onApply(ctx, payload)),
|
...(await auth.onApply(ctx, payload)),
|
||||||
@@ -269,7 +284,7 @@ export class PluginInstance {
|
|||||||
const action = this.#mod.authentication.actions?.[payload.index];
|
const action = this.#mod.authentication.actions?.[payload.index];
|
||||||
if (typeof action?.onSelect === 'function') {
|
if (typeof action?.onSelect === 'function') {
|
||||||
await action.onSelect(ctx, payload.args);
|
await action.onSelect(ctx, payload.args);
|
||||||
this.#sendEmpty(windowContext, replyId);
|
this.#sendEmpty(context, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +296,7 @@ export class PluginInstance {
|
|||||||
const action = this.#mod.httpRequestActions[payload.index];
|
const action = this.#mod.httpRequestActions[payload.index];
|
||||||
if (typeof action?.onSelect === 'function') {
|
if (typeof action?.onSelect === 'function') {
|
||||||
await action.onSelect(ctx, payload.args);
|
await action.onSelect(ctx, payload.args);
|
||||||
this.#sendEmpty(windowContext, replyId);
|
this.#sendEmpty(context, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,7 +308,7 @@ export class PluginInstance {
|
|||||||
const action = this.#mod.grpcRequestActions[payload.index];
|
const action = this.#mod.grpcRequestActions[payload.index];
|
||||||
if (typeof action?.onSelect === 'function') {
|
if (typeof action?.onSelect === 'function') {
|
||||||
await action.onSelect(ctx, payload.args);
|
await action.onSelect(ctx, payload.args);
|
||||||
this.#sendEmpty(windowContext, replyId);
|
this.#sendEmpty(context, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,12 +318,27 @@ export class PluginInstance {
|
|||||||
Array.isArray(this.#mod?.templateFunctions)
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
) {
|
) {
|
||||||
const fn = this.#mod.templateFunctions.find((a) => a.name === payload.name);
|
const fn = this.#mod.templateFunctions.find((a) => a.name === payload.name);
|
||||||
if (typeof fn?.onRender === 'function') {
|
if (
|
||||||
applyFormInputDefaults(fn.args, payload.args.values);
|
payload.args.purpose === 'preview' &&
|
||||||
|
(fn?.previewType === 'click' || fn?.previewType === 'none')
|
||||||
|
) {
|
||||||
|
// Send empty render response
|
||||||
|
this.#sendPayload(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
type: 'call_template_function_response',
|
||||||
|
value: null,
|
||||||
|
error: 'Live preview disabled for this function',
|
||||||
|
},
|
||||||
|
replyId,
|
||||||
|
);
|
||||||
|
} else if (typeof fn?.onRender === 'function') {
|
||||||
|
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, payload.args);
|
||||||
|
payload.args.values = applyFormInputDefaults(resolvedArgs, payload.args.values);
|
||||||
try {
|
try {
|
||||||
const result = await fn.onRender(ctx, payload.args);
|
const result = await fn.onRender(ctx, payload.args);
|
||||||
this.#sendPayload(
|
this.#sendPayload(
|
||||||
windowContext,
|
context,
|
||||||
{
|
{
|
||||||
type: 'call_template_function_response',
|
type: 'call_template_function_response',
|
||||||
value: result ?? null,
|
value: result ?? null,
|
||||||
@@ -317,7 +347,7 @@ export class PluginInstance {
|
|||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.#sendPayload(
|
this.#sendPayload(
|
||||||
windowContext,
|
context,
|
||||||
{
|
{
|
||||||
type: 'call_template_function_response',
|
type: 'call_template_function_response',
|
||||||
value: null,
|
value: null,
|
||||||
@@ -332,12 +362,12 @@ export class PluginInstance {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = `${err}`.replace(/^Error:\s*/g, '');
|
const error = `${err}`.replace(/^Error:\s*/g, '');
|
||||||
console.log('Plugin call threw exception', payload.type, '→', error);
|
console.log('Plugin call threw exception', payload.type, '→', error);
|
||||||
this.#sendPayload(windowContext, { type: 'error_response', error }, replyId);
|
this.#sendPayload(context, { type: 'error_response', error }, replyId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No matches, so send back an empty response so the caller doesn't block forever
|
// No matches, so send back an empty response so the caller doesn't block forever
|
||||||
this.#sendEmpty(windowContext, replyId);
|
this.#sendEmpty(context, replyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pathMod() {
|
#pathMod() {
|
||||||
@@ -360,7 +390,7 @@ export class PluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#buildEventToSend(
|
#buildEventToSend(
|
||||||
windowContext: PluginWindowContext,
|
context: PluginContext,
|
||||||
payload: InternalEventPayload,
|
payload: InternalEventPayload,
|
||||||
replyId: string | null = null,
|
replyId: string | null = null,
|
||||||
): InternalEvent {
|
): InternalEvent {
|
||||||
@@ -370,16 +400,16 @@ export class PluginInstance {
|
|||||||
id: genId(),
|
id: genId(),
|
||||||
replyId,
|
replyId,
|
||||||
payload,
|
payload,
|
||||||
windowContext,
|
context,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendPayload(
|
#sendPayload(
|
||||||
windowContext: PluginWindowContext,
|
context: PluginContext,
|
||||||
payload: InternalEventPayload,
|
payload: InternalEventPayload,
|
||||||
replyId: string | null,
|
replyId: string | null,
|
||||||
): string {
|
): string {
|
||||||
const event = this.#buildEventToSend(windowContext, payload, replyId);
|
const event = this.#buildEventToSend(context, payload, replyId);
|
||||||
this.#sendEvent(event);
|
this.#sendEvent(event);
|
||||||
return event.id;
|
return event.id;
|
||||||
}
|
}
|
||||||
@@ -391,16 +421,16 @@ export class PluginInstance {
|
|||||||
this.#pluginToAppEvents.emit(event);
|
this.#pluginToAppEvents.emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendEmpty(windowContext: PluginWindowContext, replyId: string | null = null): string {
|
#sendEmpty(context: PluginContext, replyId: string | null = null): string {
|
||||||
return this.#sendPayload(windowContext, { type: 'empty_response' }, replyId);
|
return this.#sendPayload(context, { type: 'empty_response' }, replyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
#sendForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||||
windowContext: PluginWindowContext,
|
context: PluginContext,
|
||||||
payload: InternalEventPayload,
|
payload: InternalEventPayload,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
// 1. Build event to send
|
// 1. Build event to send
|
||||||
const eventToSend = this.#buildEventToSend(windowContext, payload, null);
|
const eventToSend = this.#buildEventToSend(context, payload, null);
|
||||||
|
|
||||||
// 2. Spawn listener in background
|
// 2. Spawn listener in background
|
||||||
const promise = new Promise<T>((resolve) => {
|
const promise = new Promise<T>((resolve) => {
|
||||||
@@ -422,12 +452,12 @@ export class PluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#sendAndListenForEvents(
|
#sendAndListenForEvents(
|
||||||
windowContext: PluginWindowContext,
|
context: PluginContext,
|
||||||
payload: InternalEventPayload,
|
payload: InternalEventPayload,
|
||||||
onEvent: (event: InternalEventPayload) => void,
|
onEvent: (event: InternalEventPayload) => void,
|
||||||
): void {
|
): void {
|
||||||
// 1. Build event to send
|
// 1. Build event to send
|
||||||
const eventToSend = this.#buildEventToSend(windowContext, payload, null);
|
const eventToSend = this.#buildEventToSend(context, payload, null);
|
||||||
|
|
||||||
// 2. Listen for replies in the background
|
// 2. Listen for replies in the background
|
||||||
this.#appToPluginEvents.listen((event: InternalEvent) => {
|
this.#appToPluginEvents.listen((event: InternalEvent) => {
|
||||||
@@ -440,11 +470,23 @@ export class PluginInstance {
|
|||||||
this.#sendEvent(eventToSend);
|
this.#sendEvent(eventToSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
#newCtx(windowContext: PluginWindowContext): Context {
|
#newCtx(context: PluginContext): Context {
|
||||||
|
const _windowInfo = async () => {
|
||||||
|
if (context.label == null) {
|
||||||
|
throw new Error("Can't get window context without an active window");
|
||||||
|
}
|
||||||
|
const payload: InternalEventPayload = {
|
||||||
|
type: 'window_info_request',
|
||||||
|
label: context.label,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.#sendForReply<WindowInfoResponse>(context, payload);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
copyText: async (text) => {
|
copyText: async (text) => {
|
||||||
await this.#sendAndWaitForReply(windowContext, {
|
await this.#sendForReply(context, {
|
||||||
type: 'copy_text_request',
|
type: 'copy_text_request',
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
@@ -452,13 +494,24 @@ export class PluginInstance {
|
|||||||
},
|
},
|
||||||
toast: {
|
toast: {
|
||||||
show: async (args) => {
|
show: async (args) => {
|
||||||
await this.#sendAndWaitForReply(windowContext, {
|
await this.#sendForReply(context, {
|
||||||
type: 'show_toast_request',
|
type: 'show_toast_request',
|
||||||
|
// Handle default here because null/undefined both convert to None in Rust translation
|
||||||
|
timeout: args.timeout === undefined ? 5000 : args.timeout,
|
||||||
...args,
|
...args,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
|
requestId: async () => {
|
||||||
|
return (await _windowInfo()).requestId;
|
||||||
|
},
|
||||||
|
async workspaceId(): Promise<string | null> {
|
||||||
|
return (await _windowInfo()).workspaceId;
|
||||||
|
},
|
||||||
|
async environmentId(): Promise<string | null> {
|
||||||
|
return (await _windowInfo()).environmentId;
|
||||||
|
},
|
||||||
openUrl: async ({ onNavigate, onClose, ...args }) => {
|
openUrl: async ({ onNavigate, onClose, ...args }) => {
|
||||||
args.label = args.label || `${Math.random()}`;
|
args.label = args.label || `${Math.random()}`;
|
||||||
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
|
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
|
||||||
@@ -469,21 +522,21 @@ export class PluginInstance {
|
|||||||
onClose?.();
|
onClose?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.#sendAndListenForEvents(windowContext, payload, onEvent);
|
this.#sendAndListenForEvents(context, payload, onEvent);
|
||||||
return {
|
return {
|
||||||
close: () => {
|
close: () => {
|
||||||
const closePayload: InternalEventPayload = {
|
const closePayload: InternalEventPayload = {
|
||||||
type: 'close_window_request',
|
type: 'close_window_request',
|
||||||
label: args.label,
|
label: args.label,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, closePayload, null);
|
this.#sendPayload(context, closePayload, null);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
prompt: {
|
prompt: {
|
||||||
text: async (args) => {
|
text: async (args) => {
|
||||||
const reply: PromptTextResponse = await this.#sendAndWaitForReply(windowContext, {
|
const reply: PromptTextResponse = await this.#sendForReply(context, {
|
||||||
type: 'prompt_text_request',
|
type: 'prompt_text_request',
|
||||||
...args,
|
...args,
|
||||||
});
|
});
|
||||||
@@ -496,8 +549,8 @@ export class PluginInstance {
|
|||||||
type: 'find_http_responses_request',
|
type: 'find_http_responses_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>(
|
const { httpResponses } = await this.#sendForReply<FindHttpResponsesResponse>(
|
||||||
windowContext,
|
context,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
return httpResponses;
|
return httpResponses;
|
||||||
@@ -509,8 +562,8 @@ export class PluginInstance {
|
|||||||
type: 'render_grpc_request_request',
|
type: 'render_grpc_request_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
|
const { grpcRequest } = await this.#sendForReply<RenderGrpcRequestResponse>(
|
||||||
windowContext,
|
context,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
return grpcRequest;
|
return grpcRequest;
|
||||||
@@ -522,8 +575,8 @@ export class PluginInstance {
|
|||||||
type: 'get_http_request_by_id_request',
|
type: 'get_http_request_by_id_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
|
const { httpRequest } = await this.#sendForReply<GetHttpRequestByIdResponse>(
|
||||||
windowContext,
|
context,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
return httpRequest;
|
return httpRequest;
|
||||||
@@ -533,8 +586,8 @@ export class PluginInstance {
|
|||||||
type: 'send_http_request_request',
|
type: 'send_http_request_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
|
const { httpResponse } = await this.#sendForReply<SendHttpRequestResponse>(
|
||||||
windowContext,
|
context,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
@@ -544,8 +597,8 @@ export class PluginInstance {
|
|||||||
type: 'render_http_request_request',
|
type: 'render_http_request_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
|
const { httpRequest } = await this.#sendForReply<RenderHttpRequestResponse>(
|
||||||
windowContext,
|
context,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
return httpRequest;
|
return httpRequest;
|
||||||
@@ -557,18 +610,12 @@ export class PluginInstance {
|
|||||||
type: 'get_cookie_value_request',
|
type: 'get_cookie_value_request',
|
||||||
...args,
|
...args,
|
||||||
} as const;
|
} as const;
|
||||||
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
|
const { value } = await this.#sendForReply<GetCookieValueResponse>(context, payload);
|
||||||
windowContext,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
listNames: async () => {
|
listNames: async () => {
|
||||||
const payload = { type: 'list_cookie_names_request' } as const;
|
const payload = { type: 'list_cookie_names_request' } as const;
|
||||||
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
|
const { names } = await this.#sendForReply<ListCookieNamesResponse>(context, payload);
|
||||||
windowContext,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
return names;
|
return names;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -579,20 +626,14 @@ export class PluginInstance {
|
|||||||
*/
|
*/
|
||||||
render: async (args) => {
|
render: async (args) => {
|
||||||
const payload = { type: 'template_render_request', ...args } as const;
|
const payload = { type: 'template_render_request', ...args } as const;
|
||||||
const result = await this.#sendAndWaitForReply<TemplateRenderResponse>(
|
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
|
||||||
windowContext,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
return result.data as any;
|
return result.data as any;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
store: {
|
store: {
|
||||||
get: async <T>(key: string) => {
|
get: async <T>(key: string) => {
|
||||||
const payload = { type: 'get_key_value_request', key } as const;
|
const payload = { type: 'get_key_value_request', key } as const;
|
||||||
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(
|
const result = await this.#sendForReply<GetKeyValueResponse>(context, payload);
|
||||||
windowContext,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
return result.value ? (JSON.parse(result.value) as T) : undefined;
|
return result.value ? (JSON.parse(result.value) as T) : undefined;
|
||||||
},
|
},
|
||||||
set: async <T>(key: string, value: T) => {
|
set: async <T>(key: string, value: T) => {
|
||||||
@@ -602,20 +643,17 @@ export class PluginInstance {
|
|||||||
key,
|
key,
|
||||||
value: valueStr,
|
value: valueStr,
|
||||||
};
|
};
|
||||||
await this.#sendAndWaitForReply<GetKeyValueResponse>(windowContext, payload);
|
await this.#sendForReply<GetKeyValueResponse>(context, payload);
|
||||||
},
|
},
|
||||||
delete: async (key: string) => {
|
delete: async (key: string) => {
|
||||||
const payload = { type: 'delete_key_value_request', key } as const;
|
const payload = { type: 'delete_key_value_request', key } as const;
|
||||||
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(
|
const result = await this.#sendForReply<DeleteKeyValueResponse>(context, payload);
|
||||||
windowContext,
|
|
||||||
payload,
|
|
||||||
);
|
|
||||||
return result.deleted;
|
return result.deleted;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugin: {
|
plugin: {
|
||||||
reload: () => {
|
reload: () => {
|
||||||
this.#sendPayload({ type: 'none' }, { type: 'reload_response', silent: true }, null);
|
this.#sendPayload(context, { type: 'reload_response', silent: true }, null);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -631,20 +669,6 @@ function genId(len = 5): string {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recursively apply form input defaults to a set of values */
|
|
||||||
function applyFormInputDefaults(
|
|
||||||
inputs: TemplateFunctionArg[],
|
|
||||||
values: { [p: string]: JsonValue | undefined },
|
|
||||||
) {
|
|
||||||
for (const input of inputs) {
|
|
||||||
if ('inputs' in input) {
|
|
||||||
applyFormInputDefaults(input.inputs ?? [], values);
|
|
||||||
} else if ('defaultValue' in input && values[input.name] === undefined) {
|
|
||||||
values[input.name] = input.defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const watchedFiles: Record<string, Stats | null> = {};
|
const watchedFiles: Record<string, Stats | null> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
56
packages/plugin-runtime/src/common.ts
Normal file
56
packages/plugin-runtime/src/common.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
CallHttpAuthenticationActionArgs,
|
||||||
|
CallTemplateFunctionArgs,
|
||||||
|
JsonPrimitive,
|
||||||
|
TemplateFunctionArg,
|
||||||
|
} from '@yaakapp-internal/plugins';
|
||||||
|
import { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||||
|
|
||||||
|
/** Recursively apply form input defaults to a set of values */
|
||||||
|
export function applyFormInputDefaults(
|
||||||
|
inputs: TemplateFunctionArg[],
|
||||||
|
values: { [p: string]: JsonPrimitive | undefined },
|
||||||
|
) {
|
||||||
|
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
|
||||||
|
for (const input of inputs) {
|
||||||
|
if ('defaultValue' in input && values[input.name] === undefined) {
|
||||||
|
newValues[input.name] = input.defaultValue;
|
||||||
|
}
|
||||||
|
// Recurse down to all child inputs
|
||||||
|
if ('inputs' in input) {
|
||||||
|
newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyDynamicFormInput(
|
||||||
|
ctx: Context,
|
||||||
|
args: DynamicTemplateFunctionArg[],
|
||||||
|
callArgs: CallTemplateFunctionArgs,
|
||||||
|
): Promise<DynamicTemplateFunctionArg[]>;
|
||||||
|
|
||||||
|
export async function applyDynamicFormInput(
|
||||||
|
ctx: Context,
|
||||||
|
args: DynamicAuthenticationArg[],
|
||||||
|
callArgs: CallHttpAuthenticationActionArgs,
|
||||||
|
): Promise<DynamicAuthenticationArg[]>;
|
||||||
|
|
||||||
|
export async function applyDynamicFormInput(
|
||||||
|
ctx: Context,
|
||||||
|
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
|
||||||
|
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
|
||||||
|
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
|
||||||
|
const resolvedArgs: any[] = [];
|
||||||
|
for (const { dynamic, ...arg } of args) {
|
||||||
|
const newArg: any = {
|
||||||
|
...arg,
|
||||||
|
...(typeof dynamic === 'function' ? await dynamic(ctx, callArgs as any) : undefined),
|
||||||
|
};
|
||||||
|
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
|
||||||
|
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
|
||||||
|
}
|
||||||
|
resolvedArgs.push(newArg);
|
||||||
|
}
|
||||||
|
return resolvedArgs;
|
||||||
|
}
|
||||||
@@ -8,10 +8,15 @@ if (!port) {
|
|||||||
throw new Error('Plugin runtime missing PORT')
|
throw new Error('Plugin runtime missing PORT')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const host = process.env.HOST;
|
||||||
|
if (!host) {
|
||||||
|
throw new Error('Plugin runtime missing HOST')
|
||||||
|
}
|
||||||
|
|
||||||
const pluginToAppEvents = new EventChannel();
|
const pluginToAppEvents = new EventChannel();
|
||||||
const plugins: Record<string, PluginHandle> = {};
|
const plugins: Record<string, PluginHandle> = {};
|
||||||
|
|
||||||
const ws = new WebSocket(`ws://localhost:${port}`);
|
const ws = new WebSocket(`ws://${host}:${port}`);
|
||||||
|
|
||||||
ws.on('message', async (e: Buffer) => {
|
ws.on('message', async (e: Buffer) => {
|
||||||
try {
|
try {
|
||||||
@@ -34,7 +39,7 @@ async function handleIncoming(msg: string) {
|
|||||||
const pluginEvent: InternalEvent = JSON.parse(msg);
|
const pluginEvent: InternalEvent = JSON.parse(msg);
|
||||||
// Handle special event to bootstrap plugin
|
// Handle special event to bootstrap plugin
|
||||||
if (pluginEvent.payload.type === 'boot_request') {
|
if (pluginEvent.payload.type === 'boot_request') {
|
||||||
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.payload, pluginToAppEvents);
|
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.context, pluginEvent.payload, pluginToAppEvents);
|
||||||
plugins[pluginEvent.pluginRefId] = plugin;
|
plugins[pluginEvent.pluginRefId] = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { TemplateFunction } from '@yaakapp/api';
|
import type { TemplateFunctionPlugin } from '@yaakapp/api';
|
||||||
|
|
||||||
export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): TemplateFunction {
|
export function migrateTemplateFunctionSelectOptions(
|
||||||
|
f: TemplateFunctionPlugin,
|
||||||
|
): TemplateFunctionPlugin {
|
||||||
const migratedArgs = f.args.map((a) => {
|
const migratedArgs = f.args.map((a) => {
|
||||||
if (a.type === 'select') {
|
if (a.type === 'select') {
|
||||||
a.options = a.options.map((o) => ({
|
a.options = a.options.map((o) => ({
|
||||||
@@ -11,8 +13,5 @@ export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): Templ
|
|||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { ...f, args: migratedArgs };
|
||||||
...f,
|
|
||||||
args: migratedArgs,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
150
packages/plugin-runtime/tests/common.test.ts
Normal file
150
packages/plugin-runtime/tests/common.test.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||||
|
import { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { applyDynamicFormInput, applyFormInputDefaults } from '../src/common';
|
||||||
|
|
||||||
|
describe('applyFormInputDefaults', () => {
|
||||||
|
test('Works with top-level select', () => {
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'test',
|
||||||
|
options: [{ label: 'Option 1', value: 'one' }],
|
||||||
|
defaultValue: 'one',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(applyFormInputDefaults(args, {})).toEqual({
|
||||||
|
test: 'one',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Works with existing value', () => {
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'test',
|
||||||
|
options: [{ label: 'Option 1', value: 'one' }],
|
||||||
|
defaultValue: 'one',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(applyFormInputDefaults(args, { test: 'explicit' })).toEqual({
|
||||||
|
test: 'explicit',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Works with recursive select', () => {
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{ type: 'text', name: 'dummy', defaultValue: 'top' },
|
||||||
|
{
|
||||||
|
type: 'accordion',
|
||||||
|
label: 'Test',
|
||||||
|
inputs: [
|
||||||
|
{ type: 'text', name: 'name', defaultValue: 'hello' },
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'test',
|
||||||
|
options: [{ label: 'Option 1', value: 'one' }],
|
||||||
|
defaultValue: 'one',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(applyFormInputDefaults(args, {})).toEqual({
|
||||||
|
dummy: 'top',
|
||||||
|
test: 'one',
|
||||||
|
name: 'hello',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Works with dynamic options', () => {
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'test',
|
||||||
|
defaultValue: 'one',
|
||||||
|
options: [],
|
||||||
|
dynamic() {
|
||||||
|
return { options: [{ label: 'Option 1', value: 'one' }] };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(applyFormInputDefaults(args, {})).toEqual({
|
||||||
|
test: 'one',
|
||||||
|
});
|
||||||
|
expect(applyFormInputDefaults(args, {})).toEqual({
|
||||||
|
test: 'one',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('applyDynamicFormInput', () => {
|
||||||
|
test('Works with plain input', async () => {
|
||||||
|
const ctx = {} as Context;
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{ type: 'text', name: 'name' },
|
||||||
|
{ type: 'checkbox', name: 'checked' },
|
||||||
|
];
|
||||||
|
const callArgs: CallTemplateFunctionArgs = {
|
||||||
|
values: {},
|
||||||
|
purpose: 'preview',
|
||||||
|
};
|
||||||
|
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
|
||||||
|
{ type: 'text', name: 'name' },
|
||||||
|
{ type: 'checkbox', name: 'checked' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Works with dynamic input', async () => {
|
||||||
|
const ctx = {} as Context;
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'name',
|
||||||
|
async dynamic(_ctx, _args) {
|
||||||
|
return { hidden: true };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const callArgs: CallTemplateFunctionArgs = {
|
||||||
|
values: {},
|
||||||
|
purpose: 'preview',
|
||||||
|
};
|
||||||
|
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
|
||||||
|
{ type: 'text', name: 'name', hidden: true },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Works with recursive dynamic input', async () => {
|
||||||
|
const ctx = {} as Context;
|
||||||
|
const callArgs: CallTemplateFunctionArgs = {
|
||||||
|
values: { hello: 'world' },
|
||||||
|
purpose: 'preview',
|
||||||
|
};
|
||||||
|
const args: DynamicTemplateFunctionArg[] = [
|
||||||
|
{
|
||||||
|
type: 'banner',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'name',
|
||||||
|
async dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.hello === 'world' };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(await applyDynamicFormInput(ctx, args, callArgs)).toEqual([
|
||||||
|
{
|
||||||
|
type: 'banner',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'name',
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -43,6 +43,26 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
|
|||||||
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
|
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add API key authentication
|
||||||
|
if (request.authenticationType === 'apikey') {
|
||||||
|
if (request.authentication?.location === 'query') {
|
||||||
|
const sep = finalUrl.includes('?') ? '&' : '?';
|
||||||
|
finalUrl = [
|
||||||
|
finalUrl,
|
||||||
|
sep,
|
||||||
|
encodeURIComponent(request.authentication?.key ?? 'token'),
|
||||||
|
'=',
|
||||||
|
encodeURIComponent(request.authentication?.value ?? ''),
|
||||||
|
].join('');
|
||||||
|
} else {
|
||||||
|
request.headers = request.headers ?? [];
|
||||||
|
request.headers.push({
|
||||||
|
name: request.authentication?.key ?? 'X-Api-Key',
|
||||||
|
value: request.authentication?.value ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
xs.push(quote(finalUrl));
|
xs.push(quote(finalUrl));
|
||||||
xs.push(NEWLINE);
|
xs.push(NEWLINE);
|
||||||
|
|
||||||
@@ -82,21 +102,49 @@ export async function convertToCurl(request: Partial<HttpRequest>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add basic/digest authentication
|
// Add basic/digest authentication
|
||||||
if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {
|
if (request.authentication?.disabled !== true) {
|
||||||
if (request.authenticationType === 'digest') xs.push('--digest');
|
if (request.authenticationType === 'basic' || request.authenticationType === 'digest') {
|
||||||
xs.push(
|
if (request.authenticationType === 'digest') xs.push('--digest');
|
||||||
'--user',
|
xs.push(
|
||||||
quote(`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`),
|
'--user',
|
||||||
);
|
quote(
|
||||||
xs.push(NEWLINE);
|
`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`,
|
||||||
}
|
),
|
||||||
|
);
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
// Add bearer authentication
|
// Add bearer authentication
|
||||||
if (request.authenticationType === 'bearer') {
|
if (request.authenticationType === 'bearer') {
|
||||||
const value =
|
const value =
|
||||||
`${request.authentication?.prefix ?? 'Bearer'} ${request.authentication?.token ?? ''}`.trim();
|
`${request.authentication?.prefix ?? 'Bearer'} ${request.authentication?.token ?? ''}`.trim();
|
||||||
xs.push('--header', quote(`Authorization: ${value}`));
|
xs.push('--header', quote(`Authorization: ${value}`));
|
||||||
xs.push(NEWLINE);
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.authenticationType === 'auth-aws-sig-v4') {
|
||||||
|
xs.push(
|
||||||
|
'--aws-sigv4',
|
||||||
|
[
|
||||||
|
'aws',
|
||||||
|
'amz',
|
||||||
|
request.authentication?.region ?? '',
|
||||||
|
request.authentication?.service ?? '',
|
||||||
|
].join(':'),
|
||||||
|
);
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
xs.push(
|
||||||
|
'--user',
|
||||||
|
quote(
|
||||||
|
`${request.authentication?.accessKeyId ?? ''}:${request.authentication?.secretAccessKey ?? ''}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (request.authentication?.sessionToken) {
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
xs.push('--header', quote(`X-Amz-Security-Token: ${request.authentication.sessionToken}`));
|
||||||
|
}
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove trailing newline
|
// Remove trailing newline
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ describe('exporter-curl', () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(` \\n `));
|
).toEqual([`curl 'https://yaak.app/path?a=aaa&b=bbb#section'`].join(` \\n `));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Exports POST with url form data', async () => {
|
test('Exports POST with url form data', async () => {
|
||||||
expect(
|
expect(
|
||||||
await convertToCurl({
|
await convertToCurl({
|
||||||
@@ -170,6 +171,20 @@ describe('exporter-curl', () => {
|
|||||||
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(` \\\n `));
|
).toEqual([`curl 'https://yaak.app'`, `--user 'user:pass'`].join(` \\\n `));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Basic auth disabled', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {
|
||||||
|
disabled: true,
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
test('Broken basic auth', async () => {
|
test('Broken basic auth', async () => {
|
||||||
expect(
|
expect(
|
||||||
await convertToCurl({
|
await convertToCurl({
|
||||||
@@ -246,6 +261,145 @@ describe('exporter-curl', () => {
|
|||||||
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(` \\\n `));
|
).toEqual([`curl 'https://yaak.app'`, `--header 'Authorization: Bearer'`].join(` \\\n `));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('AWS v4 auth', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'auth-aws-sig-v4',
|
||||||
|
authentication: {
|
||||||
|
accessKeyId: 'ak',
|
||||||
|
secretAccessKey: 'sk',
|
||||||
|
sessionToken: '',
|
||||||
|
region: 'us-east-1',
|
||||||
|
service: 's3',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[`curl 'https://yaak.app'`, `--aws-sigv4 aws:amz:us-east-1:s3`, `--user 'ak:sk'`].join(
|
||||||
|
` \\\n `,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AWS v4 auth with session', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'auth-aws-sig-v4',
|
||||||
|
authentication: {
|
||||||
|
accessKeyId: 'ak',
|
||||||
|
secretAccessKey: 'sk',
|
||||||
|
sessionToken: 'st',
|
||||||
|
region: 'us-east-1',
|
||||||
|
service: 's3',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
[
|
||||||
|
`curl 'https://yaak.app'`,
|
||||||
|
`--aws-sigv4 aws:amz:us-east-1:s3`,
|
||||||
|
`--user 'ak:sk'`,
|
||||||
|
`--header 'X-Amz-Security-Token: st'`,
|
||||||
|
].join(` \\\n `),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth header', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'header',
|
||||||
|
key: 'X-Header',
|
||||||
|
value: 'my-token',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Header: my-token'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth header query', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app?hi=there',
|
||||||
|
urlParameters: [{ name: 'param', value: 'hi' }],
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
key: 'foo',
|
||||||
|
value: 'bar',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app?hi=there¶m=hi&foo=bar'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth header query with params', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
urlParameters: [{ name: 'param', value: 'hi' }],
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
key: 'foo',
|
||||||
|
value: 'bar',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app?param=hi&foo=bar'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth header default', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'header',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app'`, `--header 'X-Api-Key: '`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth query', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
key: 'foo',
|
||||||
|
value: 'bar-baz',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app?foo=bar-baz'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth query with existing', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app?foo=bar&baz=qux',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
key: 'hi',
|
||||||
|
value: 'there',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&hi=there'`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth query default', async () => {
|
||||||
|
expect(
|
||||||
|
await convertToCurl({
|
||||||
|
url: 'https://yaak.app?foo=bar&baz=qux',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual([`curl 'https://yaak.app?foo=bar&baz=qux&token='`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
test('Stale body data', async () => {
|
test('Stale body data', async () => {
|
||||||
expect(
|
expect(
|
||||||
await convertToCurl({
|
await convertToCurl({
|
||||||
|
|||||||
@@ -68,16 +68,37 @@ export async function convert(request: Partial<GrpcRequest>, allProtoFiles: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add basic authentication
|
// Add basic authentication
|
||||||
if (request.authenticationType === 'basic') {
|
if (request.authentication?.disabled !== true) {
|
||||||
const user = request.authentication?.username ?? '';
|
if (request.authenticationType === 'basic') {
|
||||||
const pass = request.authentication?.password ?? '';
|
const user = request.authentication?.username ?? '';
|
||||||
const encoded = btoa(`${user}:${pass}`);
|
const pass = request.authentication?.password ?? '';
|
||||||
xs.push('-H', quote(`Authorization: Basic ${encoded}`));
|
const encoded = btoa(`${user}:${pass}`);
|
||||||
xs.push(NEWLINE);
|
xs.push('-H', quote(`Authorization: Basic ${encoded}`));
|
||||||
} else if (request.authenticationType === 'bearer') {
|
xs.push(NEWLINE);
|
||||||
// Add bearer authentication
|
} else if (request.authenticationType === 'bearer') {
|
||||||
xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
|
// Add bearer authentication
|
||||||
xs.push(NEWLINE);
|
xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`));
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
} else if (request.authenticationType === 'apikey') {
|
||||||
|
if (request.authentication?.location === 'query') {
|
||||||
|
const sep = request.url?.includes('?') ? '&' : '?';
|
||||||
|
request.url = [
|
||||||
|
request.url,
|
||||||
|
sep,
|
||||||
|
encodeURIComponent(request.authentication?.key ?? 'token'),
|
||||||
|
'=',
|
||||||
|
encodeURIComponent(request.authentication?.value ?? ''),
|
||||||
|
].join('');
|
||||||
|
} else {
|
||||||
|
xs.push(
|
||||||
|
'-H',
|
||||||
|
quote(
|
||||||
|
`${request.authentication?.key ?? 'X-Api-Key'}: ${request.authentication?.value ?? ''}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
xs.push(NEWLINE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add form params
|
// Add form params
|
||||||
|
|||||||
@@ -27,6 +27,55 @@ describe('exporter-curl', () => {
|
|||||||
),
|
),
|
||||||
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, `yaak.app`].join(` \\\n `));
|
).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, `yaak.app`].join(` \\\n `));
|
||||||
});
|
});
|
||||||
|
test('Basic auth', async () => {
|
||||||
|
expect(
|
||||||
|
await convert(
|
||||||
|
{
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'basic',
|
||||||
|
authentication: {
|
||||||
|
username: 'user',
|
||||||
|
password: 'pass',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
).toEqual([`grpcurl -H 'Authorization: Basic dXNlcjpwYXNz'`, `yaak.app`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth', async () => {
|
||||||
|
expect(
|
||||||
|
await convert(
|
||||||
|
{
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
key: 'X-Token',
|
||||||
|
value: 'tok',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
).toEqual([`grpcurl -H 'X-Token: tok'`, `yaak.app`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API key auth', async () => {
|
||||||
|
expect(
|
||||||
|
await convert(
|
||||||
|
{
|
||||||
|
url: 'https://yaak.app',
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: 'query',
|
||||||
|
key: 'token',
|
||||||
|
value: 'tok 1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
).toEqual([`grpcurl`, `yaak.app?token=tok%201`].join(` \\\n `));
|
||||||
|
});
|
||||||
|
|
||||||
test('Single proto file', async () => {
|
test('Single proto file', async () => {
|
||||||
expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual(
|
expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual(
|
||||||
[
|
[
|
||||||
|
|||||||
49
plugins/auth-aws/README.md
Normal file
49
plugins/auth-aws/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# AWS Signature Version 4 Auth
|
||||||
|
|
||||||
|
A plugin for authenticating AWS-compatible requests using the
|
||||||
|
[AWS Signature Version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
|
||||||
|
This enables secure, signed requests to AWS services (or any S3-compatible APIs like
|
||||||
|
Cloudflare R2).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This plugin provides AWS Signature authentication for API requests in Yaak. SigV4 is used
|
||||||
|
by nearly all AWS APIs to verify the authenticity and integrity of requests using
|
||||||
|
cryptographic signatures.
|
||||||
|
|
||||||
|
With this plugin, you can securely sign requests to AWS services such as S3, STS, Lambda,
|
||||||
|
API Gateway, DynamoDB, and more. You can also authenticate against S3-compatible services
|
||||||
|
like **Cloudflare R2**, **MinIO**, or **Wasabi**.
|
||||||
|
|
||||||
|
## How AWS Signature Version 4 Works
|
||||||
|
|
||||||
|
SigV4 signs requests by creating a hash of key request components (method, URL, headers,
|
||||||
|
and optionally the payload) using your AWS credentials. The resulting HMAC signature is
|
||||||
|
added in the `Authorization` header along with credential scope metadata.
|
||||||
|
|
||||||
|
Example header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: AWS4-HMAC-SHA256 Credential=AKIA…/20251011/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abcdef123456…
|
||||||
|
```
|
||||||
|
|
||||||
|
Each request must include a timestamp (`X-Amz-Date`) and may include a session token if
|
||||||
|
using temporary credentials.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The plugin presents the following fields:
|
||||||
|
|
||||||
|
- **Access Key ID** – Your AWS access key identifier
|
||||||
|
- **Secret Access Key** – The secret associated with the access key
|
||||||
|
- **Session Token** *(optional)* – Used for temporary or assumed-role credentials (treated as secret)
|
||||||
|
- **Region** – AWS region (e.g., `us-east-1`)
|
||||||
|
- **Service** – AWS service identifier (e.g., `sts`, `s3`, `execute-api`)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Configure a request, folder, or workspace to use **AWS SigV4 Authentication**
|
||||||
|
2. Enter your AWS credentials and target service/region
|
||||||
|
3. The plugin will automatically sign outgoing requests with valid SigV4 headers
|
||||||
23
plugins/auth-aws/package.json
Normal file
23
plugins/auth-aws/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/auth-aws",
|
||||||
|
"displayName": "AWS SigV4",
|
||||||
|
"description": "Authenticate requests using AWS SigV4 signing",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mountain-loop/yaak.git",
|
||||||
|
"directory": "plugins/auth-aws"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"aws4": "^1.13.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/aws4": "^1.11.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
plugins/auth-aws/screenshot.png
Normal file
BIN
plugins/auth-aws/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 790 KiB |
88
plugins/auth-aws/src/index.ts
Normal file
88
plugins/auth-aws/src/index.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import type { CallHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
|
||||||
|
import type { PluginDefinition } from '@yaakapp/api';
|
||||||
|
import aws4 from 'aws4';
|
||||||
|
import type { Request } from 'aws4';
|
||||||
|
import { URL } from 'node:url';
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
authentication: {
|
||||||
|
name: 'awsv4',
|
||||||
|
label: 'AWS Signature',
|
||||||
|
shortLabel: 'AWS v4',
|
||||||
|
args: [
|
||||||
|
{ name: 'accessKeyId', label: 'Access Key ID', type: 'text', password: true },
|
||||||
|
{
|
||||||
|
name: 'secretAccessKey',
|
||||||
|
label: 'Secret Access Key',
|
||||||
|
type: 'text',
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'service',
|
||||||
|
label: 'Service Name',
|
||||||
|
type: 'text',
|
||||||
|
defaultValue: 'sts',
|
||||||
|
placeholder: 'sts',
|
||||||
|
description: 'The service that is receiving the request (sts, s3, sqs, ...)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'region',
|
||||||
|
label: 'Region',
|
||||||
|
type: 'text',
|
||||||
|
placeholder: 'us-east-1',
|
||||||
|
description: 'The region that is receiving the request (defaults to us-east-1)',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sessionToken',
|
||||||
|
label: 'Session Token',
|
||||||
|
type: 'text',
|
||||||
|
password: true,
|
||||||
|
optional: true,
|
||||||
|
description: 'Only required if you are using temporary credentials',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onApply(_ctx, { values, ...args }): CallHttpAuthenticationResponse {
|
||||||
|
const accessKeyId = String(values.accessKeyId || '');
|
||||||
|
const secretAccessKey = String(values.secretAccessKey || '');
|
||||||
|
const sessionToken = String(values.sessionToken || '') || undefined;
|
||||||
|
|
||||||
|
const url = new URL(args.url);
|
||||||
|
|
||||||
|
const headers: NonNullable<Request['headers']> = {};
|
||||||
|
for (const headerName of ['content-type', 'host', 'x-amz-date', 'x-amz-security-token']) {
|
||||||
|
const v = args.headers.find((h) => h.name.toLowerCase() === headerName);
|
||||||
|
if (v != null) {
|
||||||
|
headers[headerName] = v.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = aws4.sign(
|
||||||
|
{
|
||||||
|
host: url.host,
|
||||||
|
method: args.method,
|
||||||
|
path: url.pathname + (url.search || ''),
|
||||||
|
service: String(values.service || 'sts'),
|
||||||
|
region: values.region ? String(values.region) : undefined,
|
||||||
|
body: values.body ? String(values.body) : undefined,
|
||||||
|
headers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey,
|
||||||
|
sessionToken,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (signature.headers == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setHeaders: Object.entries(signature.headers)
|
||||||
|
.filter(([name]) => name !== 'content-type') // Don't add this because we already have it
|
||||||
|
.map(([name, value]) => ({ name, value: String(value || '') })),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
3
plugins/auth-aws/tsconfig.json
Normal file
3
plugins/auth-aws/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -46,6 +46,50 @@ export const plugin: PluginDefinition = {
|
|||||||
name: 'secretBase64',
|
name: 'secretBase64',
|
||||||
label: 'Secret is base64 encoded',
|
label: 'Secret is base64 encoded',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'location',
|
||||||
|
label: 'Behavior',
|
||||||
|
defaultValue: 'header',
|
||||||
|
options: [
|
||||||
|
{ label: 'Insert Header', value: 'header' },
|
||||||
|
{ label: 'Append Query Parameter', value: 'query' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Header Name',
|
||||||
|
defaultValue: 'Authorization',
|
||||||
|
optional: true,
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
if (args.values.location === 'query') {
|
||||||
|
return {
|
||||||
|
label: 'Parameter Name',
|
||||||
|
description: 'The name of the query parameter to add to the request',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
label: 'Header Name',
|
||||||
|
description: 'The name of the header to add to the request',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'headerPrefix',
|
||||||
|
label: 'Header Prefix',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: 'Bearer',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
if (args.values.location === 'query') {
|
||||||
|
return {
|
||||||
|
hidden: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'editor',
|
type: 'editor',
|
||||||
name: 'payload',
|
name: 'payload',
|
||||||
@@ -61,8 +105,17 @@ export const plugin: PluginDefinition = {
|
|||||||
const token = jwt.sign(`${payload}`, secret, {
|
const token = jwt.sign(`${payload}`, secret, {
|
||||||
algorithm: algorithm as (typeof algorithms)[number],
|
algorithm: algorithm as (typeof algorithms)[number],
|
||||||
});
|
});
|
||||||
const value = `Bearer ${token}`;
|
|
||||||
return { setHeaders: [{ name: 'Authorization', value }] };
|
if (values.location === 'query') {
|
||||||
|
const paramName = String(values.name || 'token');
|
||||||
|
const paramValue = String(values.value || '');
|
||||||
|
return { setQueryParameters: [{ name: paramName, value: paramValue }] };
|
||||||
|
} else {
|
||||||
|
const headerPrefix = values.headerPrefix != null ? values.headerPrefix : 'Bearer';
|
||||||
|
const headerName = String(values.name || 'Authorization');
|
||||||
|
const headerValue = `${headerPrefix} ${token}`.trim();
|
||||||
|
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
20
plugins/auth-ntlm/package.json
Normal file
20
plugins/auth-ntlm/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/auth-ntlm",
|
||||||
|
"displayName": "NTLM Authentication",
|
||||||
|
"description": "Authenticate requests using NTLM authentication",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mountain-loop/yaak.git",
|
||||||
|
"directory": "plugins/auth-ntlm"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"httpntlm": "^1.8.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
76
plugins/auth-ntlm/src/index.ts
Normal file
76
plugins/auth-ntlm/src/index.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import type { PluginDefinition } from '@yaakapp/api';
|
||||||
|
|
||||||
|
import { ntlm } from 'httpntlm';
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
authentication: {
|
||||||
|
name: 'windows',
|
||||||
|
label: 'NTLM Auth',
|
||||||
|
shortLabel: 'NTLM',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
optional: true,
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'accordion',
|
||||||
|
label: 'Advanced',
|
||||||
|
inputs: [
|
||||||
|
{ name: 'domain', label: 'Domain', type: 'text', optional: true },
|
||||||
|
{ name: 'workstation', label: 'Workstation', type: 'text', optional: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async onApply(ctx, { values, method, url }) {
|
||||||
|
const username = values.username ? String(values.username) : undefined;
|
||||||
|
const password = values.password ? String(values.password) : undefined;
|
||||||
|
const domain = values.domain ? String(values.domain) : undefined;
|
||||||
|
const workstation = values.workstation ? String(values.workstation) : undefined;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
workstation,
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const type1 = ntlm.createType1Message(options);
|
||||||
|
|
||||||
|
const negotiateResponse = await ctx.httpRequest.send({
|
||||||
|
httpRequest: {
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
headers: [
|
||||||
|
{ name: 'Authorization', value: type1 },
|
||||||
|
{ name: 'Connection', value: 'keep-alive' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const wwwAuthenticateHeader = negotiateResponse.headers.find(
|
||||||
|
(h) => h.name.toLowerCase() === 'www-authenticate',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!wwwAuthenticateHeader?.value) {
|
||||||
|
throw new Error('Unable to find www-authenticate response header for NTLM');
|
||||||
|
}
|
||||||
|
|
||||||
|
const type2 = ntlm.parseType2Message(wwwAuthenticateHeader.value, (err: Error | null) => {
|
||||||
|
if (err != null) throw err;
|
||||||
|
});
|
||||||
|
const type3 = ntlm.createType3Message(type2, options);
|
||||||
|
|
||||||
|
return { setHeaders: [{ name: 'Authorization', value: type3 }] };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
1
plugins/auth-ntlm/src/modules.d.ts
vendored
Normal file
1
plugins/auth-ntlm/src/modules.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module 'httpntlm';
|
||||||
3
plugins/auth-ntlm/tsconfig.json
Normal file
3
plugins/auth-ntlm/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
20
plugins/auth-oauth1/package.json
Normal file
20
plugins/auth-oauth1/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/auth-oauth1",
|
||||||
|
"displayName": "OAuth 1.0",
|
||||||
|
"description": "Authenticate requests using OAuth 1.0a",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mountain-loop/yaak.git",
|
||||||
|
"directory": "plugins/auth-oauth1"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"oauth-1.0a": "^2.2.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
197
plugins/auth-oauth1/src/index.ts
Normal file
197
plugins/auth-oauth1/src/index.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import type { Context, GetHttpAuthenticationConfigRequest, PluginDefinition } from '@yaakapp/api';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import OAuth from 'oauth-1.0a';
|
||||||
|
|
||||||
|
const signatures = {
|
||||||
|
HMAC_SHA1: 'HMAC-SHA1',
|
||||||
|
HMAC_SHA256: 'HMAC-SHA256',
|
||||||
|
HMAC_SHA512: 'HMAC-SHA512',
|
||||||
|
RSA_SHA1: 'RSA-SHA1',
|
||||||
|
RSA_SHA256: 'RSA-SHA256',
|
||||||
|
RSA_SHA512: 'RSA-SHA512',
|
||||||
|
PLAINTEXT: 'PLAINTEXT',
|
||||||
|
} as const;
|
||||||
|
const defaultSig = signatures.HMAC_SHA1;
|
||||||
|
|
||||||
|
const pkSigs = Object.values(signatures).filter((k) => k.startsWith('RSA-'));
|
||||||
|
const nonPkSigs = Object.values(signatures).filter((k) => !pkSigs.includes(k));
|
||||||
|
|
||||||
|
type SigMethod = (typeof signatures)[keyof typeof signatures];
|
||||||
|
|
||||||
|
function hiddenIfNot(
|
||||||
|
sigMethod: SigMethod[],
|
||||||
|
...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]
|
||||||
|
) {
|
||||||
|
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
|
||||||
|
const hasGrantType = sigMethod.find((t) => t === String(values.signatureMethod ?? defaultSig));
|
||||||
|
const hasOtherBools = other.every((t) => t(values));
|
||||||
|
const show = hasGrantType && hasOtherBools;
|
||||||
|
return { hidden: !show };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
authentication: {
|
||||||
|
name: 'oauth1',
|
||||||
|
label: 'OAuth 1.0',
|
||||||
|
shortLabel: 'OAuth 1',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'signatureMethod',
|
||||||
|
label: 'Signature Method',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: defaultSig,
|
||||||
|
options: Object.values(signatures).map((v) => ({ label: v, value: v })),
|
||||||
|
},
|
||||||
|
{ name: 'consumerKey', label: 'Consumer Key', type: 'text', password: true, optional: true },
|
||||||
|
{
|
||||||
|
name: 'consumerSecret',
|
||||||
|
label: 'Consumer Secret',
|
||||||
|
type: 'text',
|
||||||
|
password: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tokenKey',
|
||||||
|
label: 'Access Token',
|
||||||
|
type: 'text',
|
||||||
|
password: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tokenSecret',
|
||||||
|
label: 'Token Secret',
|
||||||
|
type: 'text',
|
||||||
|
password: true,
|
||||||
|
optional: true,
|
||||||
|
dynamic: hiddenIfNot(nonPkSigs),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'privateKey',
|
||||||
|
label: 'Private Key (RSA-SHA1)',
|
||||||
|
type: 'text',
|
||||||
|
multiLine: true,
|
||||||
|
optional: true,
|
||||||
|
password: true,
|
||||||
|
placeholder:
|
||||||
|
'-----BEGIN RSA PRIVATE KEY-----\nPrivate key in PEM format\n-----END RSA PRIVATE KEY-----',
|
||||||
|
dynamic: hiddenIfNot(pkSigs),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'accordion',
|
||||||
|
label: 'Advanced',
|
||||||
|
inputs: [
|
||||||
|
{ name: 'callback', label: 'Callback Url', type: 'text', optional: true },
|
||||||
|
{ name: 'verifier', label: 'Verifier', type: 'text', optional: true, password: true },
|
||||||
|
{ name: 'timestamp', label: 'Timestamp', type: 'text', optional: true },
|
||||||
|
{ name: 'nonce', label: 'Nonce', type: 'text', optional: true },
|
||||||
|
{
|
||||||
|
name: 'version',
|
||||||
|
label: 'OAuth Version',
|
||||||
|
type: 'text',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: '1.0',
|
||||||
|
},
|
||||||
|
{ name: 'realm', label: 'Realm', type: 'text', optional: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
onApply(
|
||||||
|
_ctx,
|
||||||
|
{ values, method, url },
|
||||||
|
): {
|
||||||
|
setHeaders?: { name: string; value: string }[];
|
||||||
|
setQueryParameters?: { name: string; value: string }[];
|
||||||
|
} {
|
||||||
|
const consumerKey = String(values.consumerKey || '');
|
||||||
|
const consumerSecret = String(values.consumerSecret || '');
|
||||||
|
|
||||||
|
const signatureMethod = String(values.signatureMethod || signatures.HMAC_SHA1) as SigMethod;
|
||||||
|
const version = String(values.version || '1.0');
|
||||||
|
const realm = String(values.realm || '') || undefined;
|
||||||
|
|
||||||
|
const oauth = new OAuth({
|
||||||
|
consumer: { key: consumerKey, secret: consumerSecret },
|
||||||
|
signature_method: signatureMethod,
|
||||||
|
version,
|
||||||
|
hash_function: hashFunction(signatureMethod),
|
||||||
|
realm,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pkSigs.includes(signatureMethod)) {
|
||||||
|
oauth.getSigningKey = (tokenSecret?: string) => tokenSecret || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestUrl = new URL(url);
|
||||||
|
|
||||||
|
// Base request options passed to oauth-1.0a
|
||||||
|
const requestData: Omit<OAuth.RequestOptions, 'data'> & {
|
||||||
|
data: Record<string, string | string[]>;
|
||||||
|
} = {
|
||||||
|
method,
|
||||||
|
url: requestUrl.toString(),
|
||||||
|
includeBodyHash: false,
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// (1) Include existing query params in signature base string
|
||||||
|
for (const key of requestUrl.searchParams.keys()) {
|
||||||
|
if (key.startsWith('oauth_')) continue;
|
||||||
|
const all = requestUrl.searchParams.getAll(key);
|
||||||
|
requestData.data[key] = all.length > 1 ? all : all[0]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (2) Manual oauth_* overrides
|
||||||
|
if (values.callback) requestData.data.oauth_callback = String(values.callback);
|
||||||
|
if (values.nonce) requestData.data.oauth_nonce = String(values.nonce);
|
||||||
|
if (values.timestamp) requestData.data.oauth_timestamp = String(values.timestamp);
|
||||||
|
if (values.verifier) requestData.data.oauth_verifier = String(values.verifier);
|
||||||
|
|
||||||
|
let token: OAuth.Token | { key: string } | undefined;
|
||||||
|
|
||||||
|
if (pkSigs.includes(signatureMethod)) {
|
||||||
|
token = {
|
||||||
|
key: String(values.tokenKey || ''),
|
||||||
|
secret: String(values.privateKey || ''),
|
||||||
|
};
|
||||||
|
} else if (values.tokenKey && values.tokenSecret) {
|
||||||
|
token = { key: String(values.tokenKey), secret: String(values.tokenSecret) };
|
||||||
|
} else if (values.tokenKey) {
|
||||||
|
token = { key: String(values.tokenKey) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const authParams = oauth.authorize(requestData, token as OAuth.Token | undefined);
|
||||||
|
const { Authorization } = oauth.toHeader(authParams);
|
||||||
|
return { setHeaders: [{ name: 'Authorization', value: Authorization }] };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function hashFunction(signatureMethod: SigMethod) {
|
||||||
|
switch (signatureMethod) {
|
||||||
|
case signatures.HMAC_SHA1:
|
||||||
|
return (base: string, key: string) =>
|
||||||
|
crypto.createHmac('sha1', key).update(base).digest('base64');
|
||||||
|
case signatures.HMAC_SHA256:
|
||||||
|
return (base: string, key: string) =>
|
||||||
|
crypto.createHmac('sha256', key).update(base).digest('base64');
|
||||||
|
case signatures.HMAC_SHA512:
|
||||||
|
return (base: string, key: string) =>
|
||||||
|
crypto.createHmac('sha512', key).update(base).digest('base64');
|
||||||
|
case signatures.RSA_SHA1:
|
||||||
|
return (base: string, privateKey: string) =>
|
||||||
|
crypto.createSign('RSA-SHA1').update(base).sign(privateKey, 'base64');
|
||||||
|
case signatures.RSA_SHA256:
|
||||||
|
return (base: string, privateKey: string) =>
|
||||||
|
crypto.createSign('RSA-SHA256').update(base).sign(privateKey, 'base64');
|
||||||
|
case signatures.RSA_SHA512:
|
||||||
|
return (base: string, privateKey: string) =>
|
||||||
|
crypto.createSign('RSA-SHA512').update(base).sign(privateKey, 'base64');
|
||||||
|
case signatures.PLAINTEXT:
|
||||||
|
return (base: string) => base;
|
||||||
|
default:
|
||||||
|
return (base: string, key: string) =>
|
||||||
|
crypto.createHmac('sha1', key).update(base).digest('base64');
|
||||||
|
}
|
||||||
|
}
|
||||||
3
plugins/auth-oauth1/tsconfig.json
Normal file
3
plugins/auth-oauth1/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yaakcli build",
|
"build": "yaakcli build",
|
||||||
"dev": "yaakcli dev",
|
"dev": "yaakcli dev",
|
||||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||||
|
"test": "vitest --run tests"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { fetchAccessToken } from '../fetchAccessToken';
|
|||||||
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
import { getOrRefreshAccessToken } from '../getOrRefreshAccessToken';
|
||||||
import type { AccessToken, TokenStoreArgs } from '../store';
|
import type { AccessToken, TokenStoreArgs } from '../store';
|
||||||
import { getDataDirKey, storeToken } from '../store';
|
import { getDataDirKey, storeToken } from '../store';
|
||||||
|
import { extractCode } from '../util';
|
||||||
|
|
||||||
export const PKCE_SHA256 = 'S256';
|
export const PKCE_SHA256 = 'S256';
|
||||||
export const PKCE_PLAIN = 'plain';
|
export const PKCE_PLAIN = 'plain';
|
||||||
@@ -79,7 +80,6 @@ export async function getAuthorizationCode(
|
|||||||
authorizationUrl.searchParams.set('code_challenge_method', pkce.challengeMethod);
|
authorizationUrl.searchParams.set('code_challenge_method', pkce.challengeMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
const logsEnabled = (await ctx.store.get('enable_logs')) ?? false;
|
|
||||||
const dataDirKey = await getDataDirKey(ctx, contextId);
|
const dataDirKey = await getDataDirKey(ctx, contextId);
|
||||||
const authorizationUrlStr = authorizationUrl.toString();
|
const authorizationUrlStr = authorizationUrl.toString();
|
||||||
console.log('[oauth2] Authorizing', authorizationUrlStr);
|
console.log('[oauth2] Authorizing', authorizationUrlStr);
|
||||||
@@ -97,18 +97,17 @@ export async function getAuthorizationCode(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onNavigate({ url: urlStr }) {
|
async onNavigate({ url: urlStr }) {
|
||||||
const url = new URL(urlStr);
|
let code;
|
||||||
if (logsEnabled) console.log('[oauth2] Navigated to', urlStr);
|
try {
|
||||||
|
code = extractCode(urlStr, redirectUri);
|
||||||
if (url.searchParams.has('error')) {
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
close();
|
close();
|
||||||
return reject(new Error(`Failed to authorize: ${url.searchParams.get('error')}`));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = url.searchParams.get('code');
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
console.log('[oauth2] Code not found');
|
return;
|
||||||
return; // Could be one of many redirects in a chain, so skip it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the window here, because we don't need it anymore!
|
// Close the window here, because we don't need it anymore!
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import type {
|
|||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
} from '@yaakapp/api';
|
} from '@yaakapp/api';
|
||||||
import {
|
import {
|
||||||
genPkceCodeVerifier,
|
|
||||||
DEFAULT_PKCE_METHOD,
|
DEFAULT_PKCE_METHOD,
|
||||||
|
genPkceCodeVerifier,
|
||||||
getAuthorizationCode,
|
getAuthorizationCode,
|
||||||
PKCE_PLAIN,
|
PKCE_PLAIN,
|
||||||
PKCE_SHA256,
|
PKCE_SHA256,
|
||||||
@@ -125,17 +125,6 @@ export const plugin: PluginDefinition = {
|
|||||||
await resetDataDirKey(ctx, contextId);
|
await resetDataDirKey(ctx, contextId);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Toggle Debug Logs',
|
|
||||||
async onSelect(ctx) {
|
|
||||||
const enableLogs = !(await ctx.store.get('enable_logs'));
|
|
||||||
await ctx.store.set('enable_logs', enableLogs);
|
|
||||||
await ctx.toast.show({
|
|
||||||
message: `Debug logs ${enableLogs ? 'enabled' : 'disabled'}`,
|
|
||||||
color: 'info',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
@@ -271,6 +260,12 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Advanced',
|
label: 'Advanced',
|
||||||
inputs: [
|
inputs: [
|
||||||
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'headerName',
|
||||||
|
label: 'Header Name',
|
||||||
|
defaultValue: 'Authorization',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'headerPrefix',
|
name: 'headerPrefix',
|
||||||
@@ -293,6 +288,7 @@ export const plugin: PluginDefinition = {
|
|||||||
{
|
{
|
||||||
type: 'accordion',
|
type: 'accordion',
|
||||||
label: 'Access Token Response',
|
label: 'Access Token Response',
|
||||||
|
inputs: [],
|
||||||
async dynamic(ctx, { contextId, values }) {
|
async dynamic(ctx, { contextId, values }) {
|
||||||
const tokenArgs: TokenStoreArgs = {
|
const tokenArgs: TokenStoreArgs = {
|
||||||
contextId,
|
contextId,
|
||||||
@@ -309,6 +305,7 @@ export const plugin: PluginDefinition = {
|
|||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
type: 'editor',
|
type: 'editor',
|
||||||
|
name: 'response',
|
||||||
defaultValue: JSON.stringify(token.response, null, 2),
|
defaultValue: JSON.stringify(token.response, null, 2),
|
||||||
hideLabel: true,
|
hideLabel: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
@@ -397,15 +394,9 @@ export const plugin: PluginDefinition = {
|
|||||||
throw new Error('Invalid grant type ' + grantType);
|
throw new Error('Invalid grant type ' + grantType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerName = stringArg(values, 'headerName') || 'Authorization';
|
||||||
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
||||||
return {
|
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||||
setHeaders: [
|
|
||||||
{
|
|
||||||
name: 'Authorization',
|
|
||||||
value: headerValue,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,3 +3,83 @@ import type { AccessToken } from './store';
|
|||||||
export function isTokenExpired(token: AccessToken) {
|
export function isTokenExpired(token: AccessToken) {
|
||||||
return token.expiresAt && Date.now() > token.expiresAt;
|
return token.expiresAt && Date.now() > token.expiresAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractCode(urlStr: string, redirectUri: string | null): string | null {
|
||||||
|
const url = new URL(urlStr);
|
||||||
|
|
||||||
|
if (!urlMatchesRedirect(url, redirectUri)) {
|
||||||
|
console.log('[oauth2] URL does not match redirect origin/path; skipping.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer query param; fall back to fragment if query lacks it
|
||||||
|
|
||||||
|
const query = url.searchParams;
|
||||||
|
const queryError = query.get('error');
|
||||||
|
const queryDesc = query.get('error_description');
|
||||||
|
const queryUri = query.get('error_uri');
|
||||||
|
|
||||||
|
let hashParams: URLSearchParams | null = null;
|
||||||
|
if (url.hash && url.hash.length > 1) {
|
||||||
|
hashParams = new URLSearchParams(url.hash.slice(1));
|
||||||
|
}
|
||||||
|
const hashError = hashParams?.get('error');
|
||||||
|
const hashDesc = hashParams?.get('error_description');
|
||||||
|
const hashUri = hashParams?.get('error_uri');
|
||||||
|
|
||||||
|
const error = queryError || hashError;
|
||||||
|
if (error) {
|
||||||
|
const desc = queryDesc || hashDesc;
|
||||||
|
const uri = queryUri || hashUri;
|
||||||
|
let message = `Failed to authorize: ${error}`;
|
||||||
|
if (desc) message += ` (${desc})`;
|
||||||
|
if (uri) message += ` [${uri}]`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryCode = query.get('code');
|
||||||
|
if (queryCode) return queryCode;
|
||||||
|
|
||||||
|
const hashCode = hashParams?.get('code');
|
||||||
|
if (hashCode) return hashCode;
|
||||||
|
|
||||||
|
console.log('[oauth2] Code not found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function urlMatchesRedirect(url: URL, redirectUrl: string | null): boolean {
|
||||||
|
if (!redirectUrl) return true;
|
||||||
|
|
||||||
|
let redirect;
|
||||||
|
try {
|
||||||
|
redirect = new URL(redirectUrl);
|
||||||
|
} catch {
|
||||||
|
console.log('[oauth2] Invalid redirect URI; skipping.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sameProtocol = url.protocol === redirect.protocol;
|
||||||
|
|
||||||
|
const sameHost = url.hostname.toLowerCase() === redirect.hostname.toLowerCase();
|
||||||
|
|
||||||
|
const normalizePort = (u: URL) =>
|
||||||
|
(u.protocol === 'https:' && (!u.port || u.port === '443')) ||
|
||||||
|
(u.protocol === 'http:' && (!u.port || u.port === '80'))
|
||||||
|
? ''
|
||||||
|
: u.port;
|
||||||
|
|
||||||
|
const samePort = normalizePort(url) === normalizePort(redirect);
|
||||||
|
|
||||||
|
const normPath = (p: string) => {
|
||||||
|
const withLeading = p.startsWith('/') ? p : `/${p}`;
|
||||||
|
// strip trailing slashes, keep root as "/"
|
||||||
|
return withLeading.replace(/\/+$/g, '') || '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Require redirect path to be a prefix of the navigated URL path
|
||||||
|
const urlPath = normPath(url.pathname);
|
||||||
|
const redirectPath = normPath(redirect.pathname);
|
||||||
|
const pathMatches = urlPath === redirectPath || urlPath.startsWith(`${redirectPath}/`);
|
||||||
|
|
||||||
|
return sameProtocol && sameHost && samePort && pathMatches;
|
||||||
|
}
|
||||||
|
|||||||
109
plugins/auth-oauth2/tests/util.test.ts
Normal file
109
plugins/auth-oauth2/tests/util.test.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import { extractCode } from '../src/util';
|
||||||
|
|
||||||
|
describe('extractCode', () => {
|
||||||
|
test('extracts code from query when same origin + path', () => {
|
||||||
|
const url = 'https://app.example.com/cb?code=abc123&state=xyz';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extracts code from query with weird path', () => {
|
||||||
|
const url = 'https://app.example.com/cbwithextra?code=abc123&state=xyz';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allows trailing slash differences', () => {
|
||||||
|
expect(extractCode('https://app.example.com/cb/?code=abc', 'https://app.example.com/cb')).toBe(
|
||||||
|
'abc',
|
||||||
|
);
|
||||||
|
expect(extractCode('https://app.example.com/cb?code=abc', 'https://app.example.com/cb/')).toBe(
|
||||||
|
'abc',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats default ports as equal (https:443, http:80)', () => {
|
||||||
|
expect(
|
||||||
|
extractCode('https://app.example.com/cb?code=abc', 'https://app.example.com:443/cb'),
|
||||||
|
).toBe('abc');
|
||||||
|
expect(extractCode('http://app.example.com/cb?code=abc', 'http://app.example.com:80/cb')).toBe(
|
||||||
|
'abc',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects different port', () => {
|
||||||
|
expect(
|
||||||
|
extractCode('https://app.example.com/cb?code=abc', 'https://app.example.com:8443/cb'),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects different hostname (including subdomain changes)', () => {
|
||||||
|
expect(
|
||||||
|
extractCode('https://evil.example.com/cb?code=abc', 'https://app.example.com/cb'),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires path to start with redirect path (ignoring query/hash)', () => {
|
||||||
|
// same origin but wrong path -> null
|
||||||
|
expect(
|
||||||
|
extractCode('https://app.example.com/other?code=abc', 'https://app.example.com/cb'),
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
// deeper subpath under the redirect path -> allowed (prefix match)
|
||||||
|
expect(
|
||||||
|
extractCode('https://app.example.com/cb/deep?code=abc', 'https://app.example.com/cb'),
|
||||||
|
).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('works with custom schemes', () => {
|
||||||
|
expect(extractCode('myapp://cb?code=abc', 'myapp://cb')).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prefers query over fragment when both present', () => {
|
||||||
|
const url = 'https://app.example.com/cb?code=queryCode#code=hashCode';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('queryCode');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extracts code from fragment when query lacks code', () => {
|
||||||
|
const url = 'https://app.example.com/cb#code=fromHash&state=xyz';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('fromHash');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null if no code present (query or fragment)', () => {
|
||||||
|
const url = 'https://app.example.com/cb?state=only';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null when provider reports an error', () => {
|
||||||
|
const url = 'https://app.example.com/cb?error=access_denied&error_description=oopsy';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(() => extractCode(url, redirect)).toThrow('Failed to authorize: access_denied');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when redirectUri is null, extracts code from any URL', () => {
|
||||||
|
expect(extractCode('https://random.example.com/whatever?code=abc', null)).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles extra params gracefully', () => {
|
||||||
|
const url = 'https://app.example.com/cb?foo=1&bar=2&code=abc&baz=3';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores fragment noise when code is in query', () => {
|
||||||
|
const url = 'https://app.example.com/cb?code=abc#some=thing';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
// If you decide NOT to support fragment-based codes, flip these to expect null or mark as .skip
|
||||||
|
test('supports fragment-only code for response_mode=fragment providers', () => {
|
||||||
|
const url = 'https://app.example.com/cb#state=xyz&code=abc';
|
||||||
|
const redirect = 'https://app.example.com/cb';
|
||||||
|
expect(extractCode(url, redirect)).toBe('abc');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
|
|
||||||
export function convertSyntax(variable: string): string {
|
|
||||||
if (!isJSString(variable)) return variable;
|
|
||||||
return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isJSObject(obj: unknown) {
|
export function isJSObject(obj: unknown) {
|
||||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||||
}
|
}
|
||||||
@@ -32,3 +26,18 @@ export function deleteUndefinedAttrs<T>(obj: T): T {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Recursively render all nested object properties */
|
||||||
|
export function convertTemplateSyntax<T>(obj: T): T {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
return obj.replaceAll(/{{\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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import type { PartialImportResources } from '@yaakapp/api';
|
import type { PartialImportResources } from '@yaakapp/api';
|
||||||
import { convertId, convertSyntax, isJSObject } from './common';
|
import { convertId, convertTemplateSyntax, isJSObject } from './common';
|
||||||
|
|
||||||
export function convertInsomniaV4(parsed: any) {
|
export function convertInsomniaV4(parsed: any) {
|
||||||
if (!Array.isArray(parsed.resources)) return null;
|
if (!Array.isArray(parsed.resources)) return null;
|
||||||
@@ -60,7 +60,7 @@ export function convertInsomniaV4(parsed: any) {
|
|||||||
resources.environments = resources.environments.filter(Boolean);
|
resources.environments = resources.environments.filter(Boolean);
|
||||||
resources.workspaces = resources.workspaces.filter(Boolean);
|
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||||
|
|
||||||
return { resources };
|
return { resources: convertTemplateSyntax(resources) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function importHttpRequest(r: any, workspaceId: string): PartialImportResources['httpRequests'][0] {
|
function importHttpRequest(r: any, workspaceId: string): PartialImportResources['httpRequests'][0] {
|
||||||
@@ -90,10 +90,10 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[
|
|||||||
};
|
};
|
||||||
} else if (r.body?.mimeType === 'application/graphql') {
|
} else if (r.body?.mimeType === 'application/graphql') {
|
||||||
bodyType = 'graphql';
|
bodyType = 'graphql';
|
||||||
body = { text: convertSyntax(r.body.text ?? '') };
|
body = { text: r.body.text ?? '' };
|
||||||
} else if (r.body?.mimeType === 'application/json') {
|
} else if (r.body?.mimeType === 'application/json') {
|
||||||
bodyType = 'application/json';
|
bodyType = 'application/json';
|
||||||
body = { text: convertSyntax(r.body.text ?? '') };
|
body = { text: r.body.text ?? '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
let authenticationType: string | null = null;
|
let authenticationType: string | null = null;
|
||||||
@@ -101,13 +101,13 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[
|
|||||||
if (r.authentication.type === 'bearer') {
|
if (r.authentication.type === 'bearer') {
|
||||||
authenticationType = 'bearer';
|
authenticationType = 'bearer';
|
||||||
authentication = {
|
authentication = {
|
||||||
token: convertSyntax(r.authentication.token),
|
token: r.authentication.token,
|
||||||
};
|
};
|
||||||
} else if (r.authentication.type === 'basic') {
|
} else if (r.authentication.type === 'basic') {
|
||||||
authenticationType = 'basic';
|
authenticationType = 'basic';
|
||||||
authentication = {
|
authentication = {
|
||||||
username: convertSyntax(r.authentication.username),
|
username: r.authentication.username,
|
||||||
password: convertSyntax(r.authentication.password),
|
password: r.authentication.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,12 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[
|
|||||||
sortPriority: r.metaSortKey,
|
sortPriority: r.metaSortKey,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description || undefined,
|
description: r.description || undefined,
|
||||||
url: convertSyntax(r.url),
|
url: r.url,
|
||||||
|
urlParameters: (r.parameters ?? []).map((p: any) => ({
|
||||||
|
enabled: !p.disabled,
|
||||||
|
name: p.name ?? '',
|
||||||
|
value: p.value ?? '',
|
||||||
|
})),
|
||||||
body,
|
body,
|
||||||
bodyType,
|
bodyType,
|
||||||
authentication,
|
authentication,
|
||||||
@@ -152,7 +157,7 @@ function importGrpcRequest(r: any, workspaceId: string): PartialImportResources[
|
|||||||
sortPriority: r.metaSortKey,
|
sortPriority: r.metaSortKey,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description || undefined,
|
description: r.description || undefined,
|
||||||
url: convertSyntax(r.url),
|
url: r.url,
|
||||||
service,
|
service,
|
||||||
method,
|
method,
|
||||||
message: r.body?.text ?? '',
|
message: r.body?.text ?? '',
|
||||||
@@ -184,15 +189,15 @@ function importEnvironment(
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
isParent?: boolean,
|
isParent?: boolean,
|
||||||
): PartialImportResources['environments'][0] {
|
): PartialImportResources['environments'][0] {
|
||||||
|
isParent ??= e.parentId === workspaceId;
|
||||||
return {
|
return {
|
||||||
id: convertId(e._id),
|
id: convertId(e._id),
|
||||||
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
|
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
|
||||||
updatedAt: e.modified ? new Date(e.modified).toISOString().replace('Z', '') : undefined,
|
updatedAt: e.modified ? new Date(e.modified).toISOString().replace('Z', '') : undefined,
|
||||||
workspaceId: convertId(workspaceId),
|
workspaceId: convertId(workspaceId),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
sortPriority: e.metaSortKey,
|
||||||
// @ts-expect-error
|
parentModel: isParent ? 'workspace' : 'environment',
|
||||||
sortPriority: e.metaSortKey, // Will be added to Yaak later
|
parentId: null,
|
||||||
base: isParent ?? e.parentId === workspaceId,
|
|
||||||
model: 'environment',
|
model: 'environment',
|
||||||
name: e.name,
|
name: e.name,
|
||||||
variables: Object.entries(e.data).map(([name, value]) => ({
|
variables: Object.entries(e.data).map(([name, value]) => ({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import type { PartialImportResources } from '@yaakapp/api';
|
import type { PartialImportResources } from '@yaakapp/api';
|
||||||
import { convertId, convertSyntax, isJSObject } from './common';
|
import { convertId, convertTemplateSyntax, isJSObject } from './common';
|
||||||
|
|
||||||
export function convertInsomniaV5(parsed: any) {
|
export function convertInsomniaV5(parsed: any) {
|
||||||
// Assert parsed is object
|
// Assert parsed is object
|
||||||
@@ -69,7 +69,7 @@ export function convertInsomniaV5(parsed: any) {
|
|||||||
resources.environments = resources.environments.filter(Boolean);
|
resources.environments = resources.environments.filter(Boolean);
|
||||||
resources.workspaces = resources.workspaces.filter(Boolean);
|
resources.workspaces = resources.workspaces.filter(Boolean);
|
||||||
|
|
||||||
return { resources };
|
return { resources: convertTemplateSyntax(resources) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function importHttpRequest(
|
function importHttpRequest(
|
||||||
@@ -108,10 +108,10 @@ function importHttpRequest(
|
|||||||
};
|
};
|
||||||
} else if (r.body?.mimeType === 'application/graphql') {
|
} else if (r.body?.mimeType === 'application/graphql') {
|
||||||
bodyType = 'graphql';
|
bodyType = 'graphql';
|
||||||
body = { text: convertSyntax(r.body.text ?? '') };
|
body = { text: r.body.text ?? '' };
|
||||||
} else if (r.body?.mimeType === 'application/json') {
|
} else if (r.body?.mimeType === 'application/json') {
|
||||||
bodyType = 'application/json';
|
bodyType = 'application/json';
|
||||||
body = { text: convertSyntax(r.body.text ?? '') };
|
body = { text: r.body.text ?? '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -124,7 +124,12 @@ function importHttpRequest(
|
|||||||
model: 'http_request',
|
model: 'http_request',
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.meta?.description || undefined,
|
description: r.meta?.description || undefined,
|
||||||
url: convertSyntax(r.url),
|
url: r.url,
|
||||||
|
urlParameters: (r.parameters ?? []).map((p: any) => ({
|
||||||
|
enabled: !p.disabled,
|
||||||
|
name: p.name ?? '',
|
||||||
|
value: p.value ?? '',
|
||||||
|
})),
|
||||||
body,
|
body,
|
||||||
bodyType,
|
bodyType,
|
||||||
method: r.method,
|
method: r.method,
|
||||||
@@ -157,7 +162,7 @@ function importGrpcRequest(
|
|||||||
sortPriority: sortKey,
|
sortPriority: sortKey,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description || undefined,
|
description: r.description || undefined,
|
||||||
url: convertSyntax(r.url),
|
url: r.url,
|
||||||
service,
|
service,
|
||||||
method,
|
method,
|
||||||
message: r.body?.text ?? '',
|
message: r.body?.text ?? '',
|
||||||
@@ -191,7 +196,7 @@ function importWebsocketRequest(
|
|||||||
sortPriority: sortKey,
|
sortPriority: sortKey,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description || undefined,
|
description: r.description || undefined,
|
||||||
url: convertSyntax(r.url),
|
url: r.url,
|
||||||
message: r.body?.text ?? '',
|
message: r.body?.text ?? '',
|
||||||
...importHeaders(r),
|
...importHeaders(r),
|
||||||
...importAuthentication(r),
|
...importAuthentication(r),
|
||||||
@@ -215,13 +220,13 @@ function importAuthentication(obj: any) {
|
|||||||
if (obj.authentication?.type === 'bearer') {
|
if (obj.authentication?.type === 'bearer') {
|
||||||
authenticationType = 'bearer';
|
authenticationType = 'bearer';
|
||||||
authentication = {
|
authentication = {
|
||||||
token: convertSyntax(obj.authentication.token),
|
token: obj.authentication.token,
|
||||||
};
|
};
|
||||||
} else if (obj.authentication?.type === 'basic') {
|
} else if (obj.authentication?.type === 'basic') {
|
||||||
authenticationType = 'basic';
|
authenticationType = 'basic';
|
||||||
authentication = {
|
authentication = {
|
||||||
username: convertSyntax(obj.authentication.username),
|
username: obj.authentication.username,
|
||||||
password: convertSyntax(obj.authentication.password),
|
password: obj.authentication.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +300,7 @@ function importEnvironment(
|
|||||||
updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined,
|
updatedAt: updated ? new Date(updated).toISOString().replace('Z', '') : undefined,
|
||||||
workspaceId: convertId(workspaceId),
|
workspaceId: convertId(workspaceId),
|
||||||
public: !e.isPrivate,
|
public: !e.isPrivate,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
sortPriority: sortKey,
|
||||||
// @ts-expect-error
|
|
||||||
sortPriority: sortKey, // Will be added to Yaak later
|
|
||||||
parentModel: isParent ? 'workspace' : 'environment',
|
parentModel: isParent ? 'workspace' : 'environment',
|
||||||
parentId: null,
|
parentId: null,
|
||||||
model: 'environment',
|
model: 'environment',
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"createdAt": "2025-01-13T15:15:43.767",
|
"createdAt": "2025-01-13T15:15:43.767",
|
||||||
"updatedAt": "2025-01-13T15:15:55.209",
|
"updatedAt": "2025-01-13T15:15:55.209",
|
||||||
"sortPriority": 1736781343767,
|
"sortPriority": 1736781343767,
|
||||||
"base": true,
|
"parentId": null,
|
||||||
|
"parentModel": "workspace",
|
||||||
"id": "GENERATE_ID::env_16c0dec5b77c414ae0e419b8f10c3701300c5900",
|
"id": "GENERATE_ID::env_16c0dec5b77c414ae0e419b8f10c3701300c5900",
|
||||||
"model": "environment",
|
"model": "environment",
|
||||||
"name": "Base Environment",
|
"name": "Base Environment",
|
||||||
@@ -22,7 +23,8 @@
|
|||||||
"createdAt": "2025-01-13T15:15:58.515",
|
"createdAt": "2025-01-13T15:15:58.515",
|
||||||
"updatedAt": "2025-01-13T15:16:34.705",
|
"updatedAt": "2025-01-13T15:16:34.705",
|
||||||
"sortPriority": 1736781358515,
|
"sortPriority": 1736781358515,
|
||||||
"base": false,
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
"id": "GENERATE_ID::env_799ae3d723ef44af91b4817e5d057e6d",
|
"id": "GENERATE_ID::env_799ae3d723ef44af91b4817e5d057e6d",
|
||||||
"model": "environment",
|
"model": "environment",
|
||||||
"name": "Production",
|
"name": "Production",
|
||||||
@@ -39,7 +41,8 @@
|
|||||||
"createdAt": "2025-01-13T15:16:14.707",
|
"createdAt": "2025-01-13T15:16:14.707",
|
||||||
"updatedAt": "2025-01-13T15:16:31.078",
|
"updatedAt": "2025-01-13T15:16:31.078",
|
||||||
"sortPriority": 1736781358565,
|
"sortPriority": 1736781358565,
|
||||||
"base": false,
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
"id": "GENERATE_ID::env_030fbfdbb274426ebd78e2e6518f8553",
|
"id": "GENERATE_ID::env_030fbfdbb274426ebd78e2e6518f8553",
|
||||||
"model": "environment",
|
"model": "environment",
|
||||||
"name": "Staging",
|
"name": "Staging",
|
||||||
@@ -110,6 +113,13 @@
|
|||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
"name": "New Request",
|
"name": "New Request",
|
||||||
"url": "${[BASE_URL ]}/foo/:id",
|
"url": "${[BASE_URL ]}/foo/:id",
|
||||||
|
"urlParameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"value": "qqq",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"workspaceId": "GENERATE_ID::wrk_d4d92f7c0ee947b89159243506687019"
|
"workspaceId": "GENERATE_ID::wrk_d4d92f7c0ee947b89159243506687019"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
"sortPriority": -1747414129276,
|
"sortPriority": -1747414129276,
|
||||||
"updatedAt": "2025-05-16T16:48:49.313",
|
"updatedAt": "2025-05-16T16:48:49.313",
|
||||||
"url": "https://httpbin.org/post",
|
"url": "https://httpbin.org/post",
|
||||||
|
"urlParameters": [],
|
||||||
"workspaceId": "GENERATE_ID::wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c"
|
"workspaceId": "GENERATE_ID::wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
"name": "New Request",
|
"name": "New Request",
|
||||||
"sortPriority": -1747414160498,
|
"sortPriority": -1747414160498,
|
||||||
"updatedAt": "2025-05-16T16:49:20.497",
|
"updatedAt": "2025-05-16T16:49:20.497",
|
||||||
|
"urlParameters": [],
|
||||||
"workspaceId": "GENERATE_ID::wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c"
|
"workspaceId": "GENERATE_ID::wrk_9717dd1c9e0c4b2e9ed6d2abcf3bd45c"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ collection:
|
|||||||
name: X-Header
|
name: X-Header
|
||||||
value: xxxx
|
value: xxxx
|
||||||
disabled: false
|
disabled: false
|
||||||
|
- id: pair_ab4b870278e943cba6babf5a73e213e3
|
||||||
|
name: "{{ _.ApiHeaderName }}"
|
||||||
|
value: "{{ _.ApiKey }}"
|
||||||
|
disabled: false
|
||||||
authentication:
|
authentication:
|
||||||
type: basic
|
type: basic
|
||||||
useISO88591: false
|
useISO88591: false
|
||||||
|
|||||||
@@ -127,6 +127,11 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"name": "X-Header",
|
"name": "X-Header",
|
||||||
"value": "xxxx"
|
"value": "xxxx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"name": "${[ApiHeaderName ]}",
|
||||||
|
"value": "${[ApiKey ]}"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "GENERATE_ID::req_d72fff2a6b104b91a2ebe9de9edd2785",
|
"id": "GENERATE_ID::req_d72fff2a6b104b91a2ebe9de9edd2785",
|
||||||
@@ -135,6 +140,13 @@
|
|||||||
"name": "New Request",
|
"name": "New Request",
|
||||||
"sortPriority": -1736781406672,
|
"sortPriority": -1736781406672,
|
||||||
"url": "${[BASE_URL ]}/foo/:id",
|
"url": "${[BASE_URL ]}/foo/:id",
|
||||||
|
"urlParameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"value": "qqq",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53"
|
"workspaceId": "GENERATE_ID::wrk_c1eacfa750a04f3ea9985ef28043fa53"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
14
plugins/importer-postman-environment/package.json
Normal file
14
plugins/importer-postman-environment/package.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/importer-postman-environment",
|
||||||
|
"displayName": "Postman Environment Importer",
|
||||||
|
"description": "Import environments from Postman",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "./build/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||||
|
"test": "vitest --run tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
138
plugins/importer-postman-environment/src/index.ts
Normal file
138
plugins/importer-postman-environment/src/index.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import type {
|
||||||
|
Context,
|
||||||
|
Environment,
|
||||||
|
PartialImportResources,
|
||||||
|
PluginDefinition,
|
||||||
|
Workspace,
|
||||||
|
} from '@yaakapp/api';
|
||||||
|
import type { ImportPluginResponse } from '@yaakapp/api/lib/plugins/ImporterPlugin';
|
||||||
|
|
||||||
|
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'>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
importer: {
|
||||||
|
name: 'Postman Environment',
|
||||||
|
description: 'Import postman environment exports',
|
||||||
|
onImport(_ctx: Context, args: { text: string }) {
|
||||||
|
return convertPostmanEnvironment(args.text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function convertPostmanEnvironment(contents: string): ImportPluginResponse | undefined {
|
||||||
|
const root = parseJSONToRecord(contents);
|
||||||
|
if (root == null) return;
|
||||||
|
|
||||||
|
// Validate that it looks like a Postman Environment export
|
||||||
|
const values = toArray<{
|
||||||
|
key?: string;
|
||||||
|
value?: unknown;
|
||||||
|
enabled?: boolean;
|
||||||
|
description?: string;
|
||||||
|
type?: string;
|
||||||
|
}>(root.values);
|
||||||
|
const scope = root._postman_variable_scope;
|
||||||
|
const hasEnvMarkers = typeof scope === 'string';
|
||||||
|
|
||||||
|
if (values.length === 0 || (!hasEnvMarkers && typeof root.name !== 'string')) {
|
||||||
|
// Not a Postman environment file, skip
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportResources: ExportResources = {
|
||||||
|
workspaces: [],
|
||||||
|
environments: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const envVariables = values
|
||||||
|
.map((v) => ({
|
||||||
|
enabled: v.enabled ?? true,
|
||||||
|
name: String(v.key ?? ''),
|
||||||
|
value: String(v.value),
|
||||||
|
description: v.description ? String(v.description) : null,
|
||||||
|
}))
|
||||||
|
.filter((v) => v.name.length > 0);
|
||||||
|
|
||||||
|
const environment: ExportResources['environments'][0] = {
|
||||||
|
model: 'environment',
|
||||||
|
id: generateId('environment'),
|
||||||
|
name: root.name ? String(root.name) : 'Environment',
|
||||||
|
workspaceId: 'CURRENT_WORKSPACE',
|
||||||
|
parentModel: 'environment',
|
||||||
|
parentId: null,
|
||||||
|
variables: envVariables,
|
||||||
|
};
|
||||||
|
exportResources.environments.push(environment);
|
||||||
|
|
||||||
|
const resources = deleteUndefinedAttrs(
|
||||||
|
convertTemplateSyntax(exportResources),
|
||||||
|
) as PartialImportResources;
|
||||||
|
|
||||||
|
return { resources };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
|
||||||
|
try {
|
||||||
|
return toRecord(JSON.parse(jsonStr));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> {
|
||||||
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
return value as Record<string, T>;
|
||||||
|
}
|
||||||
|
return {} as Record<string, T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray<T>(value: unknown): T[] {
|
||||||
|
if (Object.prototype.toString.call(value) === '[object Array]') return value as T[];
|
||||||
|
else return [] as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Recursively render all nested object properties */
|
||||||
|
function convertTemplateSyntax<T>(obj: T): T {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
return obj.replace(
|
||||||
|
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||||
|
(_m, _dot, expr) => '${[' + expr.trim() + ']}',
|
||||||
|
) 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 as Record<string, unknown>).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||||
|
) as T;
|
||||||
|
} else {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUndefinedAttrs<T>(obj: T): T {
|
||||||
|
if (Array.isArray(obj) && obj != null) {
|
||||||
|
return obj.map(deleteUndefinedAttrs) as T;
|
||||||
|
} else if (typeof obj === 'object' && obj != null) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(obj as Record<string, unknown>)
|
||||||
|
.filter(([, v]) => v !== undefined)
|
||||||
|
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
|
||||||
|
) as T;
|
||||||
|
} else {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const idCount: Partial<Record<string, number>> = {};
|
||||||
|
|
||||||
|
function generateId(model: string): string {
|
||||||
|
idCount[model] = (idCount[model] ?? -1) + 1;
|
||||||
|
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default plugin;
|
||||||
27
plugins/importer-postman-environment/tests/fixtures/environment.input.json
vendored
Normal file
27
plugins/importer-postman-environment/tests/fixtures/environment.input.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"id": "123",
|
||||||
|
"name": "My Environment",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"key": "baseUrl",
|
||||||
|
"value": "https://api.example.com",
|
||||||
|
"type": "default",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{ access_token }}",
|
||||||
|
"type": "default",
|
||||||
|
"description": "Access token for the API.",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "disabled",
|
||||||
|
"type": "secret",
|
||||||
|
"value": "hello",
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_postman_variable_scope": "environment",
|
||||||
|
"_postman_exported_using": "PostmanRuntime/1.0.0"
|
||||||
|
}
|
||||||
35
plugins/importer-postman-environment/tests/fixtures/environment.output.json
vendored
Normal file
35
plugins/importer-postman-environment/tests/fixtures/environment.output.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"resources": {
|
||||||
|
"workspaces": [],
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_0",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "My Environment",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"description": null,
|
||||||
|
"name": "baseUrl",
|
||||||
|
"value": "https://api.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"description": "Access token for the API.",
|
||||||
|
"name": "token",
|
||||||
|
"value": "${[access_token]}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"description": null,
|
||||||
|
"name": "disabled",
|
||||||
|
"value": "hello"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"workspaceId": "CURRENT_WORKSPACE",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
plugins/importer-postman-environment/tests/index.test.ts
Normal file
22
plugins/importer-postman-environment/tests/index.test.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as fs from 'node:fs';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { convertPostmanEnvironment } from '../src';
|
||||||
|
|
||||||
|
describe('importer-postman-environment', () => {
|
||||||
|
const p = path.join(__dirname, 'fixtures');
|
||||||
|
const fixtures = fs.readdirSync(p);
|
||||||
|
|
||||||
|
for (const fixture of fixtures) {
|
||||||
|
if (fixture.includes('.output')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Imports ' + fixture, () => {
|
||||||
|
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||||
|
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
||||||
|
const result = convertPostmanEnvironment(contents);
|
||||||
|
expect(result).toEqual(JSON.parse(expected));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
3
plugins/importer-postman-environment/tsconfig.json
Normal file
3
plugins/importer-postman-environment/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin
|
|||||||
id: generateId('workspace'),
|
id: generateId('workspace'),
|
||||||
name: info.name ? String(info.name) : 'Postman Import',
|
name: info.name ? String(info.name) : 'Postman Import',
|
||||||
description,
|
description,
|
||||||
|
...globalAuth,
|
||||||
};
|
};
|
||||||
exportResources.workspaces.push(workspace);
|
exportResources.workspaces.push(workspace);
|
||||||
|
|
||||||
@@ -105,8 +106,7 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin
|
|||||||
} else if (typeof v.name === 'string' && 'request' in v) {
|
} else if (typeof v.name === 'string' && 'request' in v) {
|
||||||
const r = toRecord(v.request);
|
const r = toRecord(v.request);
|
||||||
const bodyPatch = importBody(r.body);
|
const bodyPatch = importBody(r.body);
|
||||||
const requestAuthPath = importAuth(r.auth);
|
const requestAuth = importAuth(r.auth);
|
||||||
const authPatch = requestAuthPath.authenticationType == null ? globalAuth : requestAuthPath;
|
|
||||||
|
|
||||||
const headers: HttpRequestHeader[] = toArray<{
|
const headers: HttpRequestHeader[] = toArray<{
|
||||||
key: string;
|
key: string;
|
||||||
@@ -145,10 +145,9 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin
|
|||||||
urlParameters,
|
urlParameters,
|
||||||
body: bodyPatch.body,
|
body: bodyPatch.body,
|
||||||
bodyType: bodyPatch.bodyType,
|
bodyType: bodyPatch.bodyType,
|
||||||
authentication: authPatch.authentication,
|
|
||||||
authenticationType: authPatch.authenticationType,
|
|
||||||
sortPriority: sortPriorityIndex++,
|
sortPriority: sortPriorityIndex++,
|
||||||
headers,
|
headers,
|
||||||
|
...requestAuth,
|
||||||
};
|
};
|
||||||
exportResources.httpRequests.push(request);
|
exportResources.httpRequests.push(request);
|
||||||
} else {
|
} else {
|
||||||
@@ -223,25 +222,159 @@ function convertUrl(rawUrl: string | unknown): Pick<HttpRequest, 'url' | 'urlPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
function importAuth(rawAuth: unknown): Pick<HttpRequest, 'authentication' | 'authenticationType'> {
|
function importAuth(rawAuth: unknown): Pick<HttpRequest, 'authentication' | 'authenticationType'> {
|
||||||
const auth = toRecord<{ username?: string; password?: string; token?: string }>(rawAuth);
|
const auth = toRecord<Record<string, string>>(rawAuth);
|
||||||
if ('basic' in auth) {
|
|
||||||
|
// Helper: Postman stores auth params as an array of { key, value, ... }
|
||||||
|
const pmArrayToObj = (v: unknown): Record<string, unknown> => {
|
||||||
|
if (!Array.isArray(v)) return toRecord(v);
|
||||||
|
const o: Record<string, unknown> = {};
|
||||||
|
for (const i of v) {
|
||||||
|
const ii = toRecord(i);
|
||||||
|
if (typeof ii.key === 'string') {
|
||||||
|
o[ii.key] = ii.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
|
const authType: string | undefined = auth.type ? String(auth.type) : undefined;
|
||||||
|
|
||||||
|
if (authType === 'noauth') {
|
||||||
|
return {
|
||||||
|
authenticationType: 'none',
|
||||||
|
authentication: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('basic' in auth && authType === 'basic') {
|
||||||
|
const b = pmArrayToObj(auth.basic);
|
||||||
return {
|
return {
|
||||||
authenticationType: 'basic',
|
authenticationType: 'basic',
|
||||||
authentication: {
|
authentication: {
|
||||||
username: auth.basic.username || '',
|
username: String(b.username ?? ''),
|
||||||
password: auth.basic.password || '',
|
password: String(b.password ?? ''),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else if ('bearer' in auth) {
|
}
|
||||||
|
|
||||||
|
if ('bearer' in auth && authType === 'bearer') {
|
||||||
|
const b = pmArrayToObj(auth.bearer);
|
||||||
|
// Postman uses key "token"
|
||||||
return {
|
return {
|
||||||
authenticationType: 'bearer',
|
authenticationType: 'bearer',
|
||||||
authentication: {
|
authentication: {
|
||||||
token: auth.bearer.token || '',
|
token: String(b.token ?? ''),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return { authenticationType: null, authentication: {} };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('awsv4' in auth && authType === 'awsv4') {
|
||||||
|
const a = pmArrayToObj(auth.awsv4);
|
||||||
|
return {
|
||||||
|
authenticationType: 'awsv4',
|
||||||
|
authentication: {
|
||||||
|
accessKeyId: a.accessKey != null ? String(a.accessKey) : undefined,
|
||||||
|
secretAccessKey: a.secretKey != null ? String(a.secretKey) : undefined,
|
||||||
|
sessionToken: a.sessionToken != null ? String(a.sessionToken) : undefined,
|
||||||
|
region: a.region != null ? String(a.region) : undefined,
|
||||||
|
service: a.service != null ? String(a.service) : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('apikey' in auth && authType === 'apikey') {
|
||||||
|
const a = pmArrayToObj(auth.apikey);
|
||||||
|
return {
|
||||||
|
authenticationType: 'apikey',
|
||||||
|
authentication: {
|
||||||
|
location: a.in === 'query' ? 'query' : 'header',
|
||||||
|
key: a.value != null ? String(a.value) : undefined,
|
||||||
|
value: a.key != null ? String(a.key) : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('jwt' in auth && authType === 'jwt') {
|
||||||
|
const a = pmArrayToObj(auth.jwt);
|
||||||
|
return {
|
||||||
|
authenticationType: 'jwt',
|
||||||
|
authentication: {
|
||||||
|
algorithm: a.algorithm != null ? String(a.algorithm).toUpperCase() : undefined,
|
||||||
|
secret: a.secret != null ? String(a.secret) : undefined,
|
||||||
|
secretBase64: !!a.isSecretBase64Encoded,
|
||||||
|
payload: a.payload != null ? String(a.payload) : undefined,
|
||||||
|
headerPrefix: a.headerPrefix != null ? String(a.headerPrefix) : undefined,
|
||||||
|
location: a.addTokenTo === 'header' ? 'header' : 'query',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('oauth2' in auth && authType === 'oauth2') {
|
||||||
|
const o = pmArrayToObj(auth.oauth2);
|
||||||
|
|
||||||
|
let grantType = o.grant_type ? String(o.grant_type) : 'authorization_code';
|
||||||
|
let pkcePatch: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if (grantType === 'authorization_code_with_pkce') {
|
||||||
|
grantType = 'authorization_code';
|
||||||
|
pkcePatch =
|
||||||
|
o.grant_type === 'authorization_code_with_pkce'
|
||||||
|
? {
|
||||||
|
usePkce: true,
|
||||||
|
pkceChallengeMethod: o.challengeAlgorithm ?? undefined,
|
||||||
|
pkceCodeVerifier: o.code_verifier != null ? String(o.code_verifier) : undefined,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
} else if (grantType === 'password_credentials') {
|
||||||
|
grantType = 'password';
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessTokenUrl = o.accessTokenUrl != null ? String(o.accessTokenUrl) : undefined;
|
||||||
|
const audience = o.audience != null ? String(o.audience) : undefined;
|
||||||
|
const authorizationUrl = o.authUrl != null ? String(o.authUrl) : undefined;
|
||||||
|
const clientId = o.clientId != null ? String(o.clientId) : undefined;
|
||||||
|
const clientSecret = o.clientSecret != null ? String(o.clientSecret) : undefined;
|
||||||
|
const credentials = o.client_authentication === 'body' ? 'body' : undefined;
|
||||||
|
const headerPrefix = o.headerPrefix ?? 'Bearer';
|
||||||
|
const password = o.password != null ? String(o.password) : undefined;
|
||||||
|
const redirectUri = o.redirect_uri != null ? String(o.redirect_uri) : undefined;
|
||||||
|
const scope = o.scope != null ? String(o.scope) : undefined;
|
||||||
|
const state = o.state != null ? String(o.state) : undefined;
|
||||||
|
const username = o.username != null ? String(o.username) : undefined;
|
||||||
|
|
||||||
|
let grantPatch: Record<string, unknown> = {};
|
||||||
|
if (grantType === 'authorization_code') {
|
||||||
|
grantPatch = {
|
||||||
|
clientSecret,
|
||||||
|
authorizationUrl,
|
||||||
|
accessTokenUrl,
|
||||||
|
redirectUri,
|
||||||
|
state,
|
||||||
|
...pkcePatch,
|
||||||
|
};
|
||||||
|
} else if (grantType === 'implicit') {
|
||||||
|
grantPatch = { authorizationUrl, redirectUri, state };
|
||||||
|
} else if (grantType === 'password') {
|
||||||
|
grantPatch = { clientSecret, accessTokenUrl, username, password };
|
||||||
|
} else if (grantType === 'client_credentials') {
|
||||||
|
grantPatch = { clientSecret, accessTokenUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
const authentication = {
|
||||||
|
name: 'oauth2',
|
||||||
|
grantType,
|
||||||
|
audience,
|
||||||
|
clientId,
|
||||||
|
credentials,
|
||||||
|
headerPrefix,
|
||||||
|
scope,
|
||||||
|
...grantPatch,
|
||||||
|
} as Record<string, unknown>;
|
||||||
|
|
||||||
|
return { authenticationType: 'oauth2', authentication };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { authenticationType: null, authentication: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | 'headers'> {
|
function importBody(rawBody: unknown): Pick<HttpRequest, 'body' | 'bodyType' | 'headers'> {
|
||||||
@@ -376,7 +509,10 @@ function toArray<T>(value: unknown): T[] {
|
|||||||
/** Recursively render all nested object properties */
|
/** Recursively render all nested object properties */
|
||||||
function convertTemplateSyntax<T>(obj: T): T {
|
function convertTemplateSyntax<T>(obj: T): T {
|
||||||
if (typeof obj === 'string') {
|
if (typeof obj === 'string') {
|
||||||
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}') as T;
|
return obj.replace(
|
||||||
|
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||||
|
(_m, _dot, expr) => '${[' + expr.trim().replace(/^vault:/, '') + ']}',
|
||||||
|
) as T;
|
||||||
} else if (Array.isArray(obj) && obj != null) {
|
} else if (Array.isArray(obj) && obj != null) {
|
||||||
return obj.map(convertTemplateSyntax) as T;
|
return obj.map(convertTemplateSyntax) as T;
|
||||||
} else if (typeof obj === 'object' && obj != null) {
|
} else if (typeof obj === 'object' && obj != null) {
|
||||||
|
|||||||
828
plugins/importer-postman/tests/fixtures/auth.input.json
vendored
Normal file
828
plugins/importer-postman/tests/fixtures/auth.input.json
vendored
Normal file
@@ -0,0 +1,828 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "9e6dfada-256c-49ea-a38f-7d1b05b7ca2d",
|
||||||
|
"name": "Authentication",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
|
"_exporter_id": "18798"
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "No Auth",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "noauth"
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "https://yaak.app/x/echo",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"yaak",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"x",
|
||||||
|
"echo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Inherit",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "https://yaak.app/x/echo",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"yaak",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"x",
|
||||||
|
"echo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth 2 Auth Code",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "authorization_code",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "client_authentication",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "challengeAlgorithm",
|
||||||
|
"value": "S256",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "refreshTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "state",
|
||||||
|
"value": "state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "scope",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "code_verifier",
|
||||||
|
"value": "verifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientSecret",
|
||||||
|
"value": "clientsecet",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientId",
|
||||||
|
"value": "cliend id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "authUrl",
|
||||||
|
"value": "https://github.com/login/oauth/authorize",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "useBrowser",
|
||||||
|
"value": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "redirect_uri",
|
||||||
|
"value": "https://callback",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenName",
|
||||||
|
"value": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"hello\": \"world\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{vault:hello}}",
|
||||||
|
"host": [
|
||||||
|
"{{vault:hello}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth 2 Auth Code (PKCE)",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "client_authentication",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "challengeAlgorithm",
|
||||||
|
"value": "S256",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "refreshTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "state",
|
||||||
|
"value": "state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "scope",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "code_verifier",
|
||||||
|
"value": "verifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "authorization_code_with_pkce",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientSecret",
|
||||||
|
"value": "clientsecet",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientId",
|
||||||
|
"value": "cliend id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "authUrl",
|
||||||
|
"value": "https://github.com/login/oauth/authorize",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "useBrowser",
|
||||||
|
"value": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "redirect_uri",
|
||||||
|
"value": "https://callback",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenName",
|
||||||
|
"value": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"hello\": \"world\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{vault:hello}}",
|
||||||
|
"host": [
|
||||||
|
"{{vault:hello}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth 2 Implicit",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "client_authentication",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "redirect_uri",
|
||||||
|
"value": "https://yaak.app/x/echo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "useBrowser",
|
||||||
|
"value": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "implicit",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "challengeAlgorithm",
|
||||||
|
"value": "S256",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "refreshTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "state",
|
||||||
|
"value": "state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "scope",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "code_verifier",
|
||||||
|
"value": "verifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientSecret",
|
||||||
|
"value": "clientsecet",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientId",
|
||||||
|
"value": "cliend id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "authUrl",
|
||||||
|
"value": "https://github.com/login/oauth/authorize",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenName",
|
||||||
|
"value": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"hello\": \"world\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{vault:hello}}",
|
||||||
|
"host": [
|
||||||
|
"{{vault:hello}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth 2 Password",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "password",
|
||||||
|
"value": "password",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"value": "username",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientSecret",
|
||||||
|
"value": "clientsecret",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientId",
|
||||||
|
"value": "clientid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "password_credentials",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "client_authentication",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "redirect_uri",
|
||||||
|
"value": "https://yaak.app/x/echo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "useBrowser",
|
||||||
|
"value": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "challengeAlgorithm",
|
||||||
|
"value": "S256",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "refreshTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "state",
|
||||||
|
"value": "state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "scope",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "code_verifier",
|
||||||
|
"value": "verifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "authUrl",
|
||||||
|
"value": "https://github.com/login/oauth/authorize",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenName",
|
||||||
|
"value": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"hello\": \"world\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{vault:hello}}",
|
||||||
|
"host": [
|
||||||
|
"{{vault:hello}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OAuth 2 Client Credentials",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "grant_type",
|
||||||
|
"value": "client_credentials",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "password",
|
||||||
|
"value": "password",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"value": "username",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientSecret",
|
||||||
|
"value": "clientsecret",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "clientId",
|
||||||
|
"value": "clientid",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "client_authentication",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "redirect_uri",
|
||||||
|
"value": "https://yaak.app/x/echo",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "useBrowser",
|
||||||
|
"value": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "challengeAlgorithm",
|
||||||
|
"value": "S256",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "refreshTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "state",
|
||||||
|
"value": "state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scope",
|
||||||
|
"value": "scope",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "code_verifier",
|
||||||
|
"value": "verifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "authUrl",
|
||||||
|
"value": "https://github.com/login/oauth/authorize",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessTokenUrl",
|
||||||
|
"value": "https://github.com/login/oauth/access_token",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tokenName",
|
||||||
|
"value": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"hello\": \"world\"\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{vault:hello}}",
|
||||||
|
"host": [
|
||||||
|
"{{vault:hello}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AWS V4",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "awsv4",
|
||||||
|
"awsv4": [
|
||||||
|
{
|
||||||
|
"key": "sessionToken",
|
||||||
|
"value": "session",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "service",
|
||||||
|
"value": "s3",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "region",
|
||||||
|
"value": "us-west-1",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secretKey",
|
||||||
|
"value": "secret",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "accessKey",
|
||||||
|
"value": "access",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "https://yaak.app/x/echo",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"yaak",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"x",
|
||||||
|
"echo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "API Key",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "apikey",
|
||||||
|
"apikey": [
|
||||||
|
{
|
||||||
|
"key": "in",
|
||||||
|
"value": "query",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
"value": "value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "key",
|
||||||
|
"value": "key",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "https://yaak.app/x/echo",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"yaak",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"x",
|
||||||
|
"echo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JWT",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "jwt",
|
||||||
|
"jwt": [
|
||||||
|
{
|
||||||
|
"key": "header",
|
||||||
|
"value": "{\n \"header\": \"foo\"\n}",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headerPrefix",
|
||||||
|
"value": "Bearer",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "payload",
|
||||||
|
"value": "{\n \"my\": \"payload\"\n}",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "isSecretBase64Encoded",
|
||||||
|
"value": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "secret",
|
||||||
|
"value": "mysecret",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "algorithm",
|
||||||
|
"value": "HS384",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "queryParamKey",
|
||||||
|
"value": "token",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "https://yaak.app/x/echo",
|
||||||
|
"protocol": "https",
|
||||||
|
"host": [
|
||||||
|
"yaak",
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"x",
|
||||||
|
"echo"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"type": "basic",
|
||||||
|
"basic": [
|
||||||
|
{
|
||||||
|
"key": "password",
|
||||||
|
"value": "workspace_secret",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"value": "workspace",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"packages": {},
|
||||||
|
"requests": {},
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"packages": {},
|
||||||
|
"requests": {},
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "COLLECTION VARIABLE",
|
||||||
|
"value": "collection variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
304
plugins/importer-postman/tests/fixtures/auth.output.json
vendored
Normal file
304
plugins/importer-postman/tests/fixtures/auth.output.json
vendored
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
{
|
||||||
|
"resources": {
|
||||||
|
"workspaces": [
|
||||||
|
{
|
||||||
|
"model": "workspace",
|
||||||
|
"id": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"name": "Authentication",
|
||||||
|
"authenticationType": "basic",
|
||||||
|
"authentication": {
|
||||||
|
"username": "workspace",
|
||||||
|
"password": "workspace_secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"model": "environment",
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_0",
|
||||||
|
"name": "Global Variables",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"parentModel": "workspace",
|
||||||
|
"parentId": null,
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "COLLECTION VARIABLE",
|
||||||
|
"value": "collection variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"httpRequests": [
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_0",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "No Auth",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://yaak.app/x/echo",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {},
|
||||||
|
"bodyType": null,
|
||||||
|
"sortPriority": 0,
|
||||||
|
"headers": [],
|
||||||
|
"authenticationType": "none",
|
||||||
|
"authentication": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_1",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "Inherit",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://yaak.app/x/echo",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {},
|
||||||
|
"bodyType": null,
|
||||||
|
"sortPriority": 1,
|
||||||
|
"headers": [],
|
||||||
|
"authenticationType": null,
|
||||||
|
"authentication": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_2",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "OAuth 2 Auth Code",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "${[hello]}",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {
|
||||||
|
"text": "{\n \"hello\": \"world\"\n}"
|
||||||
|
},
|
||||||
|
"bodyType": "application/json",
|
||||||
|
"sortPriority": 2,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authenticationType": "oauth2",
|
||||||
|
"authentication": {
|
||||||
|
"name": "oauth2",
|
||||||
|
"grantType": "authorization_code",
|
||||||
|
"clientId": "cliend id",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"scope": "scope",
|
||||||
|
"clientSecret": "clientsecet",
|
||||||
|
"authorizationUrl": "https://github.com/login/oauth/authorize",
|
||||||
|
"accessTokenUrl": "https://github.com/login/oauth/access_token",
|
||||||
|
"redirectUri": "https://callback",
|
||||||
|
"state": "state"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_3",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "OAuth 2 Auth Code (PKCE)",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "${[hello]}",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {
|
||||||
|
"text": "{\n \"hello\": \"world\"\n}"
|
||||||
|
},
|
||||||
|
"bodyType": "application/json",
|
||||||
|
"sortPriority": 3,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authenticationType": "oauth2",
|
||||||
|
"authentication": {
|
||||||
|
"name": "oauth2",
|
||||||
|
"grantType": "authorization_code",
|
||||||
|
"clientId": "cliend id",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"scope": "scope",
|
||||||
|
"clientSecret": "clientsecet",
|
||||||
|
"authorizationUrl": "https://github.com/login/oauth/authorize",
|
||||||
|
"accessTokenUrl": "https://github.com/login/oauth/access_token",
|
||||||
|
"redirectUri": "https://callback",
|
||||||
|
"state": "state",
|
||||||
|
"usePkce": true,
|
||||||
|
"pkceChallengeMethod": "S256",
|
||||||
|
"pkceCodeVerifier": "verifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_4",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "OAuth 2 Implicit",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "${[hello]}",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {
|
||||||
|
"text": "{\n \"hello\": \"world\"\n}"
|
||||||
|
},
|
||||||
|
"bodyType": "application/json",
|
||||||
|
"sortPriority": 4,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authenticationType": "oauth2",
|
||||||
|
"authentication": {
|
||||||
|
"name": "oauth2",
|
||||||
|
"grantType": "implicit",
|
||||||
|
"clientId": "cliend id",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"scope": "scope",
|
||||||
|
"authorizationUrl": "https://github.com/login/oauth/authorize",
|
||||||
|
"redirectUri": "https://yaak.app/x/echo",
|
||||||
|
"state": "state"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_5",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "OAuth 2 Password",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "${[hello]}",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {
|
||||||
|
"text": "{\n \"hello\": \"world\"\n}"
|
||||||
|
},
|
||||||
|
"bodyType": "application/json",
|
||||||
|
"sortPriority": 5,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authenticationType": "oauth2",
|
||||||
|
"authentication": {
|
||||||
|
"name": "oauth2",
|
||||||
|
"grantType": "password",
|
||||||
|
"clientId": "clientid",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"scope": "scope",
|
||||||
|
"clientSecret": "clientsecret",
|
||||||
|
"accessTokenUrl": "https://github.com/login/oauth/access_token",
|
||||||
|
"username": "username",
|
||||||
|
"password": "password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_6",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "OAuth 2 Client Credentials",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "${[hello]}",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {
|
||||||
|
"text": "{\n \"hello\": \"world\"\n}"
|
||||||
|
},
|
||||||
|
"bodyType": "application/json",
|
||||||
|
"sortPriority": 6,
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"authenticationType": "oauth2",
|
||||||
|
"authentication": {
|
||||||
|
"name": "oauth2",
|
||||||
|
"grantType": "client_credentials",
|
||||||
|
"clientId": "clientid",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"scope": "scope",
|
||||||
|
"clientSecret": "clientsecret",
|
||||||
|
"accessTokenUrl": "https://github.com/login/oauth/access_token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_7",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "AWS V4",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://yaak.app/x/echo",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {},
|
||||||
|
"bodyType": null,
|
||||||
|
"sortPriority": 7,
|
||||||
|
"headers": [],
|
||||||
|
"authenticationType": "awsv4",
|
||||||
|
"authentication": {
|
||||||
|
"accessKeyId": "access",
|
||||||
|
"secretAccessKey": "secret",
|
||||||
|
"sessionToken": "session",
|
||||||
|
"region": "us-west-1",
|
||||||
|
"service": "s3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_8",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "API Key",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://yaak.app/x/echo",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {},
|
||||||
|
"bodyType": null,
|
||||||
|
"sortPriority": 8,
|
||||||
|
"headers": [],
|
||||||
|
"authenticationType": "apikey",
|
||||||
|
"authentication": {
|
||||||
|
"location": "query",
|
||||||
|
"key": "value",
|
||||||
|
"value": "key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "http_request",
|
||||||
|
"id": "GENERATE_ID::HTTP_REQUEST_9",
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
"folderId": null,
|
||||||
|
"name": "JWT",
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://yaak.app/x/echo",
|
||||||
|
"urlParameters": [],
|
||||||
|
"body": {},
|
||||||
|
"bodyType": null,
|
||||||
|
"sortPriority": 9,
|
||||||
|
"headers": [],
|
||||||
|
"authenticationType": "jwt",
|
||||||
|
"authentication": {
|
||||||
|
"algorithm": "HS384",
|
||||||
|
"secret": "mysecret",
|
||||||
|
"secretBase64": false,
|
||||||
|
"payload": "{\n \"my\": \"payload\"\n}",
|
||||||
|
"headerPrefix": "Bearer",
|
||||||
|
"location": "header"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"folders": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,86 +3,88 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
{
|
{
|
||||||
"model": "workspace",
|
"model": "workspace",
|
||||||
"id": "GENERATE_ID::WORKSPACE_0",
|
"id": "GENERATE_ID::WORKSPACE_1",
|
||||||
"name": "New Collection"
|
"name": "New Collection",
|
||||||
|
"authenticationType": null,
|
||||||
|
"authentication": {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"environments": [
|
"environments": [
|
||||||
{
|
{
|
||||||
"id": "GENERATE_ID::ENVIRONMENT_0",
|
|
||||||
"model": "environment",
|
"model": "environment",
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||||
"name": "Global Variables",
|
"name": "Global Variables",
|
||||||
"variables": [],
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"parentModel": "workspace",
|
||||||
"parentId": null,
|
"parentId": null,
|
||||||
"parentModel": "workspace"
|
"variables": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"httpRequests": [
|
"httpRequests": [
|
||||||
{
|
{
|
||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
"id": "GENERATE_ID::HTTP_REQUEST_0",
|
"id": "GENERATE_ID::HTTP_REQUEST_10",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"folderId": "GENERATE_ID::FOLDER_1",
|
"folderId": "GENERATE_ID::FOLDER_1",
|
||||||
"name": "Request 1",
|
"name": "Request 1",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "",
|
"url": "",
|
||||||
"sortPriority": 2,
|
|
||||||
"urlParameters": [],
|
"urlParameters": [],
|
||||||
"body": {},
|
"body": {},
|
||||||
"bodyType": null,
|
"bodyType": null,
|
||||||
"authentication": {},
|
"sortPriority": 2,
|
||||||
|
"headers": [],
|
||||||
"authenticationType": null,
|
"authenticationType": null,
|
||||||
"headers": []
|
"authentication": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
"id": "GENERATE_ID::HTTP_REQUEST_1",
|
"id": "GENERATE_ID::HTTP_REQUEST_11",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"folderId": "GENERATE_ID::FOLDER_0",
|
"folderId": "GENERATE_ID::FOLDER_0",
|
||||||
"name": "Request 2",
|
"name": "Request 2",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"sortPriority": 3,
|
|
||||||
"url": "",
|
"url": "",
|
||||||
"urlParameters": [],
|
"urlParameters": [],
|
||||||
"body": {},
|
"body": {},
|
||||||
"bodyType": null,
|
"bodyType": null,
|
||||||
"authentication": {},
|
"sortPriority": 3,
|
||||||
|
"headers": [],
|
||||||
"authenticationType": null,
|
"authenticationType": null,
|
||||||
"headers": []
|
"authentication": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
"id": "GENERATE_ID::HTTP_REQUEST_2",
|
"id": "GENERATE_ID::HTTP_REQUEST_12",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
"sortPriority": 4,
|
|
||||||
"name": "Request 3",
|
"name": "Request 3",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "",
|
"url": "",
|
||||||
"urlParameters": [],
|
"urlParameters": [],
|
||||||
"body": {},
|
"body": {},
|
||||||
"bodyType": null,
|
"bodyType": null,
|
||||||
"authentication": {},
|
"sortPriority": 4,
|
||||||
|
"headers": [],
|
||||||
"authenticationType": null,
|
"authenticationType": null,
|
||||||
"headers": []
|
"authentication": {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"model": "folder",
|
"model": "folder",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"sortPriority": 0,
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"id": "GENERATE_ID::FOLDER_0",
|
"id": "GENERATE_ID::FOLDER_0",
|
||||||
"name": "Top Folder",
|
"name": "Top Folder",
|
||||||
"sortPriority": 0,
|
|
||||||
"folderId": null
|
"folderId": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "folder",
|
"model": "folder",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"sortPriority": 1,
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||||
"id": "GENERATE_ID::FOLDER_1",
|
"id": "GENERATE_ID::FOLDER_1",
|
||||||
"name": "Nested Folder",
|
"name": "Nested Folder",
|
||||||
"sortPriority": 1,
|
|
||||||
"folderId": "GENERATE_ID::FOLDER_0"
|
"folderId": "GENERATE_ID::FOLDER_0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "baeare",
|
"value": "my-token",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,18 +3,23 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
{
|
{
|
||||||
"model": "workspace",
|
"model": "workspace",
|
||||||
"id": "GENERATE_ID::WORKSPACE_1",
|
"id": "GENERATE_ID::WORKSPACE_2",
|
||||||
"name": "New Collection"
|
"name": "New Collection",
|
||||||
|
"authenticationType": "basic",
|
||||||
|
"authentication": {
|
||||||
|
"username": "globaluser",
|
||||||
|
"password": "globalpass"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"environments": [
|
"environments": [
|
||||||
{
|
{
|
||||||
"id": "GENERATE_ID::ENVIRONMENT_1",
|
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
|
||||||
"model": "environment",
|
"model": "environment",
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_2",
|
||||||
"name": "Global Variables",
|
"name": "Global Variables",
|
||||||
"parentId": null,
|
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||||
"parentModel": "workspace",
|
"parentModel": "workspace",
|
||||||
|
"parentId": null,
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"name": "COLLECTION VARIABLE",
|
"name": "COLLECTION VARIABLE",
|
||||||
@@ -26,11 +31,10 @@
|
|||||||
"httpRequests": [
|
"httpRequests": [
|
||||||
{
|
{
|
||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
"id": "GENERATE_ID::HTTP_REQUEST_3",
|
"id": "GENERATE_ID::HTTP_REQUEST_13",
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
"name": "Form URL",
|
"name": "Form URL",
|
||||||
"sortPriority": 0,
|
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"url": "example.com/:foo/:bar",
|
"url": "example.com/:foo/:bar",
|
||||||
"urlParameters": [
|
"urlParameters": [
|
||||||
@@ -71,10 +75,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"bodyType": "multipart/form-data",
|
"bodyType": "multipart/form-data",
|
||||||
"authentication": {
|
"sortPriority": 0,
|
||||||
"token": ""
|
|
||||||
},
|
|
||||||
"authenticationType": "bearer",
|
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "X-foo",
|
"name": "X-foo",
|
||||||
@@ -91,7 +92,11 @@
|
|||||||
"value": "multipart/form-data",
|
"value": "multipart/form-data",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"authenticationType": "bearer",
|
||||||
|
"authentication": {
|
||||||
|
"token": "my-token"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"folders": []
|
"folders": []
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ describe('importer-postman', () => {
|
|||||||
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
||||||
const result = convertPostman(contents);
|
const result = convertPostman(contents);
|
||||||
// console.log(JSON.stringify(result, null, 2))
|
// console.log(JSON.stringify(result, null, 2))
|
||||||
expect(result).toEqual(JSON.parse(expected));
|
expect(JSON.stringify(result, null, 2)).toEqual(
|
||||||
|
JSON.stringify(JSON.parse(expected), null, 2),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
12
plugins/template-function-ctx/package.json
Normal file
12
plugins/template-function-ctx/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/template-function-ctx",
|
||||||
|
"displayName": "Window Template Functions",
|
||||||
|
"description": "Template functions for accessing attributes of the current window",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
plugins/template-function-ctx/src/index.ts
Normal file
30
plugins/template-function-ctx/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { PluginDefinition } from '@yaakapp/api';
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
templateFunctions: [
|
||||||
|
{
|
||||||
|
name: 'ctx.request',
|
||||||
|
description: 'Get the ID of the currently active request',
|
||||||
|
args: [],
|
||||||
|
async onRender(ctx) {
|
||||||
|
return ctx.window.requestId();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ctx.environment',
|
||||||
|
description: 'Get the ID of the currently active environment',
|
||||||
|
args: [],
|
||||||
|
async onRender(ctx) {
|
||||||
|
return ctx.window.environmentId();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ctx.workspace',
|
||||||
|
description: 'Get the ID of the currently active workspace',
|
||||||
|
args: [],
|
||||||
|
async onRender(ctx) {
|
||||||
|
return ctx.window.workspaceId();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
3
plugins/template-function-ctx/tsconfig.json
Normal file
3
plugins/template-function-ctx/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -5,9 +5,29 @@ export const plugin: PluginDefinition = {
|
|||||||
{
|
{
|
||||||
name: 'base64.encode',
|
name: 'base64.encode',
|
||||||
description: 'Encode a value to base64',
|
description: 'Encode a value to base64',
|
||||||
args: [{ label: 'Plain Text', type: 'text', name: 'value', multiLine: true }],
|
args: [
|
||||||
|
{
|
||||||
|
label: 'Encoding',
|
||||||
|
type: 'select',
|
||||||
|
name: 'encoding',
|
||||||
|
defaultValue: 'base64',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Base64',
|
||||||
|
value: 'base64',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Base64 URL-safe',
|
||||||
|
value: 'base64url',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ label: 'Plain Text', type: 'text', name: 'value', multiLine: true },
|
||||||
|
],
|
||||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
return Buffer.from(String(args.values.value ?? '')).toString('base64');
|
return Buffer.from(String(args.values.value ?? '')).toString(
|
||||||
|
args.values.encoding === 'base64url' ? 'base64url' : 'base64',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
const UTF8 = 'utf8';
|
||||||
|
const options = [
|
||||||
|
{ label: 'ASCII', value: 'ascii' },
|
||||||
|
{ label: 'UTF-8', value: UTF8 },
|
||||||
|
{ label: 'UTF-16 LE', value: 'utf16le' },
|
||||||
|
{ label: 'Base64', value: 'base64' },
|
||||||
|
{ label: 'Base64 URL-safe', value: 'base64url' },
|
||||||
|
{ label: 'Latin-1', value: 'latin1' },
|
||||||
|
{ label: 'Hexadecimal', value: 'hex' },
|
||||||
|
];
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
templateFunctions: [
|
templateFunctions: [
|
||||||
{
|
{
|
||||||
name: 'fs.readFile',
|
name: 'fs.readFile',
|
||||||
description: 'Read the contents of a file as utf-8',
|
description: 'Read the contents of a file as utf-8',
|
||||||
args: [{ title: 'Select File', type: 'file', name: 'path', label: 'File' }],
|
args: [
|
||||||
|
{ title: 'Select File', type: 'file', name: 'path', label: 'File' },
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'encoding',
|
||||||
|
label: 'Encoding',
|
||||||
|
defaultValue: UTF8,
|
||||||
|
description: "Specifies how the file's bytes are decoded into text when read",
|
||||||
|
options,
|
||||||
|
},
|
||||||
|
],
|
||||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.path) return null;
|
if (!args.values.path || !args.values.encoding) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return fs.promises.readFile(String(args.values.path ?? ''), 'utf-8');
|
return fs.promises.readFile(String(args.values.path ?? ''), {
|
||||||
|
encoding: String(args.values.encoding ?? 'utf-8') as BufferEncoding,
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type TemplateFunctionPlugin = NonNullable<PluginDefinition['templateFunctions']>
|
|||||||
|
|
||||||
const hashFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
const hashFunctions: TemplateFunctionPlugin[] = algorithms.map(algorithm => ({
|
||||||
name: `hash.${algorithm}`,
|
name: `hash.${algorithm}`,
|
||||||
description: 'Hash a value to its hexidecimal representation',
|
description: 'Hash a value to its hexadecimal representation',
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
"description": "Template functions for working with JSON data",
|
"description": "Template functions for working with JSON data",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"main": "build/index.js",
|
||||||
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yaakcli build",
|
"build": "yaakcli build",
|
||||||
"dev": "yaakcli dev",
|
"dev": "yaakcli dev",
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
import type { XPathResult } from '@yaak/template-function-xml';
|
||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
import { JSONPath } from 'jsonpath-plus';
|
import { JSONPath } from 'jsonpath-plus';
|
||||||
|
|
||||||
|
const RETURN_FIRST = 'first';
|
||||||
|
const RETURN_ALL = 'all';
|
||||||
|
const RETURN_JOIN = 'join';
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
templateFunctions: [
|
templateFunctions: [
|
||||||
{
|
{
|
||||||
@@ -8,32 +13,58 @@ export const plugin: PluginDefinition = {
|
|||||||
description: 'Filter JSON-formatted text using JSONPath syntax',
|
description: 'Filter JSON-formatted text using JSONPath syntax',
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'editor',
|
||||||
name: 'input',
|
name: 'input',
|
||||||
label: 'Input',
|
label: 'Input',
|
||||||
multiLine: true,
|
language: 'json',
|
||||||
placeholder: '{ "foo": "bar" }',
|
placeholder: '{ "foo": "bar" }',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'h_stack',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'result',
|
||||||
|
label: 'Return Format',
|
||||||
|
defaultValue: RETURN_FIRST,
|
||||||
|
options: [
|
||||||
|
{ label: 'First result', value: RETURN_FIRST },
|
||||||
|
{ label: 'All results', value: RETURN_ALL },
|
||||||
|
{ label: 'Join with separator', value: RETURN_JOIN },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'join',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Separator',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: ', ',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.result !== RETURN_JOIN };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'formatted',
|
||||||
|
label: 'Pretty Print',
|
||||||
|
description: 'Format the output as JSON',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.result === RETURN_JOIN };
|
||||||
|
},
|
||||||
|
},
|
||||||
{ type: 'text', name: 'query', label: 'Query', placeholder: '$..foo' },
|
{ type: 'text', name: 'query', label: 'Query', placeholder: '$..foo' },
|
||||||
{ type: 'checkbox', name: 'formatted', label: 'Format Output' },
|
|
||||||
],
|
],
|
||||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(String(args.values.input));
|
return filterJSONPath(
|
||||||
const query = String(args.values.query ?? '$').trim();
|
String(args.values.input),
|
||||||
let filtered = JSONPath({ path: query, json: parsed });
|
String(args.values.query),
|
||||||
if (Array.isArray(filtered)) {
|
(args.values.result || RETURN_FIRST) as XPathResult,
|
||||||
filtered = filtered[0];
|
args.values.join == null ? null : String(args.values.join),
|
||||||
}
|
Boolean(args.values.formatted),
|
||||||
if (typeof filtered === 'string') {
|
);
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.values.formatted) {
|
|
||||||
return JSON.stringify(filtered, null, 2);
|
|
||||||
} else {
|
|
||||||
return JSON.stringify(filtered);
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -79,3 +110,41 @@ export const plugin: PluginDefinition = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type JSONPathResult = 'first' | 'join' | 'all';
|
||||||
|
|
||||||
|
export function filterJSONPath(
|
||||||
|
body: string,
|
||||||
|
path: string,
|
||||||
|
result: JSONPathResult,
|
||||||
|
join: string | null,
|
||||||
|
formatted: boolean = false,
|
||||||
|
): string {
|
||||||
|
const parsed = JSON.parse(body);
|
||||||
|
let items = JSONPath({ path, json: parsed });
|
||||||
|
|
||||||
|
if (items == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
// Already good
|
||||||
|
} else if (result === 'first') {
|
||||||
|
items = items[0] ?? '';
|
||||||
|
} else if (result === 'join') {
|
||||||
|
items = items.map((i) => objToStr(i, false)).join(join ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return objToStr(items, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function objToStr(o: unknown, formatted: boolean = false): string {
|
||||||
|
if (
|
||||||
|
Object.prototype.toString.call(o) === '[object Array]' ||
|
||||||
|
Object.prototype.toString.call(o) === '[object Object]'
|
||||||
|
) {
|
||||||
|
return formatted ? JSON.stringify(o, null, 2) : JSON.stringify(o);
|
||||||
|
} else {
|
||||||
|
return String(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yaakcli build",
|
"build": "yaakcli build",
|
||||||
"dev": "yaakcli dev",
|
"dev": "yaakcli dev",
|
||||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"slugify": "^1.6.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,174 @@
|
|||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
|
import slugify from 'slugify';
|
||||||
|
|
||||||
|
const STORE_NONE = 'none';
|
||||||
|
const STORE_FOREVER = 'forever';
|
||||||
|
const STORE_EXPIRE = 'expire';
|
||||||
|
|
||||||
|
interface Saved {
|
||||||
|
value: string;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
templateFunctions: [{
|
templateFunctions: [
|
||||||
name: 'prompt.text',
|
{
|
||||||
description: 'Prompt the user for input when sending a request',
|
name: 'prompt.text',
|
||||||
args: [
|
description: 'Prompt the user for input when sending a request',
|
||||||
{ type: 'text', name: 'title', label: 'Title' },
|
previewType: 'click',
|
||||||
{ type: 'text', name: 'label', label: 'Label', optional: true },
|
args: [
|
||||||
{ type: 'text', name: 'defaultValue', label: 'Default Value', optional: true },
|
{ type: 'text', name: 'label', label: 'Label' },
|
||||||
{ type: 'text', name: 'placeholder', label: 'Placeholder', optional: true },
|
{
|
||||||
],
|
type: 'select',
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
name: 'store',
|
||||||
if (args.purpose !== 'send') return null;
|
label: 'Store Input',
|
||||||
|
defaultValue: STORE_NONE,
|
||||||
|
options: [
|
||||||
|
{ label: 'Never', value: STORE_NONE },
|
||||||
|
{ label: 'Expire', value: STORE_EXPIRE },
|
||||||
|
{ label: 'Forever', value: STORE_FOREVER },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'h_stack',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.store === STORE_NONE };
|
||||||
|
},
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'namespace',
|
||||||
|
label: 'Namespace',
|
||||||
|
defaultValue: '${[ctx.workspace()]}',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'key',
|
||||||
|
label: 'Key (defaults to Label)',
|
||||||
|
optional: true,
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { placeholder: String(args.values.label || '') };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'ttl',
|
||||||
|
label: 'TTL (seconds)',
|
||||||
|
placeholder: '0',
|
||||||
|
defaultValue: '0',
|
||||||
|
optional: true,
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.store !== STORE_EXPIRE };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'banner',
|
||||||
|
color: 'info',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.store === STORE_NONE };
|
||||||
|
},
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'markdown',
|
||||||
|
content: '',
|
||||||
|
async dynamic(_ctx, args) {
|
||||||
|
const key = buildKey(args);
|
||||||
|
return {
|
||||||
|
content: ['Value will be saved under: `' + key + '`'].join('\n\n'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'accordion',
|
||||||
|
label: 'Advanced',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'title',
|
||||||
|
label: 'Prompt Title',
|
||||||
|
optional: true,
|
||||||
|
placeholder: 'Enter Value',
|
||||||
|
},
|
||||||
|
{ type: 'text', name: 'defaultValue', label: 'Default Value', optional: true },
|
||||||
|
{ type: 'text', name: 'placeholder', label: 'Input Placeholder', optional: true },
|
||||||
|
{ type: 'checkbox', name: 'password', label: 'Mask Value' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
|
if (args.purpose !== 'send') return null;
|
||||||
|
|
||||||
return await ctx.prompt.text({
|
if (args.values.store !== STORE_NONE && !args.values.namespace) {
|
||||||
id: `prompt-${args.values.label}`,
|
throw new Error('Namespace is required when storing values')
|
||||||
label: String(args.values.title ?? ''),
|
}
|
||||||
title: String(args.values.title ?? ''),
|
|
||||||
defaultValue: String(args.values.defaultValue),
|
const existing = await maybeGetValue(ctx, args);
|
||||||
placeholder: String(args.values.placeholder),
|
if (existing != null) {
|
||||||
});
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = await ctx.prompt.text({
|
||||||
|
id: `prompt-${args.values.label ?? 'none'}`,
|
||||||
|
label: String(args.values.label || 'Value'),
|
||||||
|
title: String(args.values.title ?? 'Enter Value'),
|
||||||
|
defaultValue: String(args.values.defaultValue ?? ''),
|
||||||
|
placeholder: String(args.values.placeholder ?? ''),
|
||||||
|
password: Boolean(args.values.password),
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error('Prompt cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.values.store !== STORE_NONE) {
|
||||||
|
await maybeSetValue(ctx, args, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function buildKey(args: CallTemplateFunctionArgs) {
|
||||||
|
return [args.values.namespace, args.values.key || args.values.label]
|
||||||
|
.filter((v) => !!v)
|
||||||
|
.map((v) => slugify(String(v), { lower: true, trim: true }))
|
||||||
|
.join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function maybeGetValue(ctx: Context, args: CallTemplateFunctionArgs) {
|
||||||
|
if (args.values.store === STORE_NONE) return null;
|
||||||
|
|
||||||
|
const existing = await ctx.store.get<Saved>(buildKey(args));
|
||||||
|
if (existing == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.values.store === STORE_FOREVER) {
|
||||||
|
return existing.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ttlSeconds = parseInt(String(args.values.ttl)) || 0;
|
||||||
|
const ageSeconds = (Date.now() - existing.createdAt) / 1000;
|
||||||
|
if (ageSeconds > ttlSeconds) {
|
||||||
|
ctx.store.delete(buildKey(args)).catch(console.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function maybeSetValue(ctx: Context, args: CallTemplateFunctionArgs, value: string) {
|
||||||
|
if (args.values.store === STORE_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.store.set<Saved>(buildKey(args), { value, createdAt: Date.now() });
|
||||||
|
}
|
||||||
|
|||||||
12
plugins/template-function-random/package.json
Normal file
12
plugins/template-function-random/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/template-function-random",
|
||||||
|
"displayName": "Random Template Functions",
|
||||||
|
"description": "Template functions for generating random values",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev",
|
||||||
|
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
plugins/template-function-random/src/index.ts
Normal file
43
plugins/template-function-random/src/index.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
templateFunctions: [
|
||||||
|
{
|
||||||
|
name: 'random.range',
|
||||||
|
description: 'Generate a random number between two values',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'min',
|
||||||
|
label: 'Minimum',
|
||||||
|
defaultValue: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'max',
|
||||||
|
label: 'Maximum',
|
||||||
|
defaultValue: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'decimals',
|
||||||
|
optional: true,
|
||||||
|
label: 'Decimal Places',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
|
const min = args.values.min ? parseInt(String(args.values.min ?? '0')) : 0;
|
||||||
|
const max = args.values.max ? parseInt(String(args.values.max ?? '1')) : 1;
|
||||||
|
const decimals = args.values.decimals
|
||||||
|
? parseInt(String(args.values.decimals ?? '0'))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let value = Math.random() * (max - min) + min;
|
||||||
|
if (decimals !== null) {
|
||||||
|
value = Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
3
plugins/template-function-random/tsconfig.json
Normal file
3
plugins/template-function-random/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { HttpUrlParameter } from '@yaakapp-internal/models';
|
import type { AnyModel, HttpUrlParameter } from '@yaakapp-internal/models';
|
||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
@@ -96,5 +96,59 @@ export const plugin: PluginDefinition = {
|
|||||||
return renderedValue;
|
return renderedValue;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'request.name',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
name: 'requestId',
|
||||||
|
label: 'Http Request',
|
||||||
|
type: 'http_request',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
|
const requestId = String(args.values.requestId ?? 'n/a');
|
||||||
|
const httpRequest = await ctx.httpRequest.getById({ id: requestId });
|
||||||
|
if (httpRequest == null) return null;
|
||||||
|
|
||||||
|
return resolvedModelName(httpRequest);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Use a common function for this, but it fails to build on windows during CI if I try importing it here
|
||||||
|
export function resolvedModelName(r: AnyModel | null): string {
|
||||||
|
if (r == null) return '';
|
||||||
|
|
||||||
|
if (!('url' in r) || r.model === 'plugin') {
|
||||||
|
return 'name' in r ? r.name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return name if it has one
|
||||||
|
if ('name' in r && r.name) {
|
||||||
|
return r.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace variable syntax with variable name
|
||||||
|
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]\s]+)\s*]}/g, '$1');
|
||||||
|
if (withoutVariables.trim() === '') {
|
||||||
|
return r.model === 'http_request'
|
||||||
|
? r.bodyType && r.bodyType === 'graphql'
|
||||||
|
? 'GraphQL Request'
|
||||||
|
: 'HTTP Request'
|
||||||
|
: r.model === 'websocket_request'
|
||||||
|
? 'WebSocket Request'
|
||||||
|
: 'gRPC Request';
|
||||||
|
}
|
||||||
|
|
||||||
|
// GRPC gets nice short names
|
||||||
|
if (r.model === 'grpc_request' && r.service != null && r.method != null) {
|
||||||
|
const shortService = r.service.split('.').pop();
|
||||||
|
return `${shortService}/${r.method}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip unnecessary protocol
|
||||||
|
const withoutProto = withoutVariables.replace(/^(http|https|ws|wss):\/\//, '');
|
||||||
|
|
||||||
|
return withoutProto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,11 +10,6 @@
|
|||||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonpath-plus": "^10.3.0",
|
"@yaak/template-function-xml": "*"
|
||||||
"xpath": "^0.0.34",
|
|
||||||
"@xmldom/xmldom": "^0.9.8"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/jsonpath": "^0.2.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,52 @@
|
|||||||
import { DOMParser } from '@xmldom/xmldom';
|
import type { JSONPathResult } from '../../template-function-json';
|
||||||
|
import { filterJSONPath } from '../../template-function-json';
|
||||||
|
import type { XPathResult } from '../../template-function-xml';
|
||||||
|
import { filterXPath } from '../../template-function-xml';
|
||||||
import type {
|
import type {
|
||||||
CallTemplateFunctionArgs,
|
CallTemplateFunctionArgs,
|
||||||
Context,
|
Context,
|
||||||
|
DynamicTemplateFunctionArg,
|
||||||
FormInput,
|
FormInput,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
RenderPurpose,
|
RenderPurpose,
|
||||||
} from '@yaakapp/api';
|
} from '@yaakapp/api';
|
||||||
import { JSONPath } from 'jsonpath-plus';
|
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import xpath from 'xpath';
|
|
||||||
|
|
||||||
const behaviorArg: FormInput = {
|
const BEHAVIOR_TTL = 'ttl';
|
||||||
type: 'select',
|
const BEHAVIOR_ALWAYS = 'always';
|
||||||
name: 'behavior',
|
const BEHAVIOR_SMART = 'smart';
|
||||||
label: 'Sending Behavior',
|
|
||||||
defaultValue: 'smart',
|
const RETURN_FIRST = 'first';
|
||||||
options: [
|
const RETURN_ALL = 'all';
|
||||||
{ label: 'When no responses', value: 'smart' },
|
const RETURN_JOIN = 'join';
|
||||||
{ label: 'Always', value: 'always' },
|
|
||||||
|
const behaviorArgs: DynamicTemplateFunctionArg = {
|
||||||
|
type: 'h_stack',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'behavior',
|
||||||
|
label: 'Sending Behavior',
|
||||||
|
defaultValue: BEHAVIOR_SMART,
|
||||||
|
options: [
|
||||||
|
{ label: 'When no responses', value: BEHAVIOR_SMART },
|
||||||
|
{ label: 'Always', value: BEHAVIOR_ALWAYS },
|
||||||
|
{ label: 'When expired', value: BEHAVIOR_TTL },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'ttl',
|
||||||
|
label: 'TTL (seconds)',
|
||||||
|
placeholder: '0',
|
||||||
|
defaultValue: '0',
|
||||||
|
description:
|
||||||
|
'Resend the request when the latest response is older than this many seconds, or if there are no responses yet. "0" means never expires',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.behavior !== BEHAVIOR_TTL };
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,13 +63,13 @@ export const plugin: PluginDefinition = {
|
|||||||
description: 'Read the value of a response header, by name',
|
description: 'Read the value of a response header, by name',
|
||||||
args: [
|
args: [
|
||||||
requestArg,
|
requestArg,
|
||||||
|
behaviorArgs,
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'header',
|
name: 'header',
|
||||||
label: 'Header Name',
|
label: 'Header Name',
|
||||||
placeholder: 'Content-Type',
|
placeholder: 'Content-Type',
|
||||||
},
|
},
|
||||||
behaviorArg,
|
|
||||||
],
|
],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request || !args.values.header) return null;
|
if (!args.values.request || !args.values.header) return null;
|
||||||
@@ -50,6 +78,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -65,13 +94,67 @@ export const plugin: PluginDefinition = {
|
|||||||
aliases: ['response'],
|
aliases: ['response'],
|
||||||
args: [
|
args: [
|
||||||
requestArg,
|
requestArg,
|
||||||
|
behaviorArgs,
|
||||||
|
{
|
||||||
|
type: 'h_stack',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'result',
|
||||||
|
label: 'Return Format',
|
||||||
|
defaultValue: RETURN_FIRST,
|
||||||
|
options: [
|
||||||
|
{ label: 'First result', value: RETURN_FIRST },
|
||||||
|
{ label: 'All results', value: RETURN_ALL },
|
||||||
|
{ label: 'Join with separator', value: RETURN_JOIN },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'join',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Separator',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: ', ',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.result !== RETURN_JOIN };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: 'JSONPath or XPath',
|
label: 'JSONPath or XPath',
|
||||||
placeholder: '$.books[0].id or /books[0]/id',
|
placeholder: '$.books[0].id or /books[0]/id',
|
||||||
|
dynamic: async (ctx, args) => {
|
||||||
|
const resp = await getResponse(ctx, {
|
||||||
|
requestId: String(args.values.request || ''),
|
||||||
|
purpose: 'preview',
|
||||||
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType =
|
||||||
|
resp?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? '';
|
||||||
|
if (contentType.includes('xml') || contentType?.includes('html')) {
|
||||||
|
return {
|
||||||
|
label: 'XPath',
|
||||||
|
placeholder: '/books[0]/id',
|
||||||
|
description: 'Enter an XPath expression used to filter the results',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
label: 'JSONPath',
|
||||||
|
placeholder: '$.books[0].id',
|
||||||
|
description: 'Enter a JSONPath expression used to filter the results',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
behaviorArg,
|
|
||||||
],
|
],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request || !args.values.path) return null;
|
if (!args.values.request || !args.values.path) return null;
|
||||||
@@ -80,6 +163,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -95,13 +179,35 @@ export const plugin: PluginDefinition = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return filterJSONPath(body, String(args.values.path || ''));
|
const result: JSONPathResult =
|
||||||
|
args.values.result === RETURN_ALL
|
||||||
|
? 'all'
|
||||||
|
: args.values.result === RETURN_JOIN
|
||||||
|
? 'join'
|
||||||
|
: 'first';
|
||||||
|
return filterJSONPath(
|
||||||
|
body,
|
||||||
|
String(args.values.path || ''),
|
||||||
|
result,
|
||||||
|
args.values.join == null ? null : String(args.values.join),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
// Probably not JSON, try XPath
|
// Probably not JSON, try XPath
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return filterXPath(body, String(args.values.path || ''));
|
const result: XPathResult =
|
||||||
|
args.values.result === RETURN_ALL
|
||||||
|
? 'all'
|
||||||
|
: args.values.result === RETURN_JOIN
|
||||||
|
? 'join'
|
||||||
|
: 'first';
|
||||||
|
return filterXPath(
|
||||||
|
body,
|
||||||
|
String(args.values.path || ''),
|
||||||
|
result,
|
||||||
|
args.values.join == null ? null : String(args.values.join),
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
// Probably not XML
|
// Probably not XML
|
||||||
}
|
}
|
||||||
@@ -113,7 +219,7 @@ export const plugin: PluginDefinition = {
|
|||||||
name: 'response.body.raw',
|
name: 'response.body.raw',
|
||||||
description: 'Access the entire response body, as text',
|
description: 'Access the entire response body, as text',
|
||||||
aliases: ['response'],
|
aliases: ['response'],
|
||||||
args: [requestArg, behaviorArg],
|
args: [requestArg, behaviorArgs],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request) return null;
|
if (!args.values.request) return null;
|
||||||
|
|
||||||
@@ -121,6 +227,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -141,45 +248,17 @@ export const plugin: PluginDefinition = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
function filterJSONPath(body: string, path: string): string {
|
|
||||||
const parsed = JSON.parse(body);
|
|
||||||
const items = JSONPath({ path, json: parsed })[0];
|
|
||||||
if (items == null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
Object.prototype.toString.call(items) === '[object Array]' ||
|
|
||||||
Object.prototype.toString.call(items) === '[object Object]'
|
|
||||||
) {
|
|
||||||
return JSON.stringify(items);
|
|
||||||
} else {
|
|
||||||
return String(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterXPath(body: string, path: string): string {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const doc: any = new DOMParser().parseFromString(body, 'text/xml');
|
|
||||||
const items = xpath.select(path, doc, false);
|
|
||||||
|
|
||||||
if (Array.isArray(items)) {
|
|
||||||
return items[0] != null ? String(items[0].firstChild ?? '') : '';
|
|
||||||
} else {
|
|
||||||
// Not sure what cases this happens in (?)
|
|
||||||
return String(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getResponse(
|
async function getResponse(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
{
|
{
|
||||||
requestId,
|
requestId,
|
||||||
behavior,
|
behavior,
|
||||||
purpose,
|
purpose,
|
||||||
|
ttl,
|
||||||
}: {
|
}: {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
behavior: string | null;
|
behavior: string | null;
|
||||||
|
ttl: string | null;
|
||||||
purpose: RenderPurpose;
|
purpose: RenderPurpose;
|
||||||
},
|
},
|
||||||
): Promise<HttpResponse | null> {
|
): Promise<HttpResponse | null> {
|
||||||
@@ -203,7 +282,11 @@ async function getResponse(
|
|||||||
const finalBehavior = behavior === 'always' && purpose === 'preview' ? 'smart' : behavior;
|
const finalBehavior = behavior === 'always' && purpose === 'preview' ? 'smart' : behavior;
|
||||||
|
|
||||||
// Send if no responses and "smart," or "always"
|
// Send if no responses and "smart," or "always"
|
||||||
if ((finalBehavior === 'smart' && response == null) || finalBehavior === 'always') {
|
if (
|
||||||
|
(finalBehavior === 'smart' && response == null) ||
|
||||||
|
finalBehavior === 'always' ||
|
||||||
|
(finalBehavior === BEHAVIOR_TTL && shouldSendExpired(response, ttl))
|
||||||
|
) {
|
||||||
// NOTE: Render inside this conditional, or we'll get infinite recursion (render->render->...)
|
// NOTE: Render inside this conditional, or we'll get infinite recursion (render->render->...)
|
||||||
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
||||||
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
||||||
@@ -211,3 +294,12 @@ async function getResponse(
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldSendExpired(response: HttpResponse | null, ttl: string | null): boolean {
|
||||||
|
if (response == null) return true;
|
||||||
|
const ttlSeconds = parseInt(ttl || '0') || 0;
|
||||||
|
if (ttlSeconds === 0) return false;
|
||||||
|
const nowMillis = Date.now();
|
||||||
|
const respMillis = new Date(response.createdAt + 'Z').getTime();
|
||||||
|
return respMillis + ttlSeconds * 1000 < nowMillis;
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,21 +52,30 @@ export const plugin: PluginDefinition = {
|
|||||||
templateFunctions: [
|
templateFunctions: [
|
||||||
{
|
{
|
||||||
name: 'timestamp.unix',
|
name: 'timestamp.unix',
|
||||||
description: 'Get the current timestamp in seconds',
|
description: 'Get the timestamp in seconds',
|
||||||
args: [],
|
args: [dateArg],
|
||||||
onRender: async () => String(Math.floor(Date.now() / 1000)),
|
onRender: async (_ctx, args) => {
|
||||||
|
const d = parseDateString(String(args.values.date ?? ''));
|
||||||
|
return String(Math.floor(d.getTime() / 1000));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'timestamp.unixMillis',
|
name: 'timestamp.unixMillis',
|
||||||
description: 'Get the current timestamp in milliseconds',
|
description: 'Get the timestamp in milliseconds',
|
||||||
args: [],
|
args: [dateArg],
|
||||||
onRender: async () => String(Date.now()),
|
onRender: async (_ctx, args) => {
|
||||||
|
const d = parseDateString(String(args.values.date ?? ''));
|
||||||
|
return String(d.getTime());
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'timestamp.iso8601',
|
name: 'timestamp.iso8601',
|
||||||
description: 'Get the current date in ISO8601 format',
|
description: 'Get the date in ISO8601 format',
|
||||||
args: [],
|
args: [dateArg],
|
||||||
onRender: async () => new Date().toISOString(),
|
onRender: async (_ctx, args) => {
|
||||||
|
const d = parseDateString(String(args.values.date ?? ''));
|
||||||
|
return d.toISOString();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'timestamp.format',
|
name: 'timestamp.format',
|
||||||
@@ -155,7 +164,7 @@ export function formatDatetime(args: {
|
|||||||
format?: string;
|
format?: string;
|
||||||
in?: ContextFn<Date>;
|
in?: ContextFn<Date>;
|
||||||
}): string {
|
}): string {
|
||||||
const { date, format = 'yyyy-MM-dd HH:mm:ss' } = args;
|
const { date, format } = args;
|
||||||
const d = parseDateString(date ?? '');
|
const d = parseDateString(date ?? '');
|
||||||
return formatDate(d, String(format), { in: args.in });
|
return formatDate(d, String(format || 'yyyy-MM-dd HH:mm:ss'), { in: args.in });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
"description": "Template functions for working with XML data",
|
"description": "Template functions for working with XML data",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"main": "build/index.js",
|
||||||
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yaakcli build",
|
"build": "yaakcli build",
|
||||||
"dev": "yaakcli dev",
|
"dev": "yaakcli dev",
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { DOMParser } from '@xmldom/xmldom';
|
|||||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||||
import xpath from 'xpath';
|
import xpath from 'xpath';
|
||||||
|
|
||||||
|
const RETURN_FIRST = 'first';
|
||||||
|
const RETURN_ALL = 'all';
|
||||||
|
const RETURN_JOIN = 'join';
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
templateFunctions: [
|
templateFunctions: [
|
||||||
{
|
{
|
||||||
@@ -15,20 +19,39 @@ export const plugin: PluginDefinition = {
|
|||||||
multiLine: true,
|
multiLine: true,
|
||||||
placeholder: '<foo></foo>',
|
placeholder: '<foo></foo>',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'h_stack',
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'result',
|
||||||
|
label: 'Return Format',
|
||||||
|
defaultValue: RETURN_FIRST,
|
||||||
|
options: [
|
||||||
|
{ label: 'First result', value: RETURN_FIRST },
|
||||||
|
{ label: 'All results', value: RETURN_ALL },
|
||||||
|
{ label: 'Join with separator', value: RETURN_JOIN },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'join',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Separator',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: ', ',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
return { hidden: args.values.result !== RETURN_JOIN };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{ type: 'text', name: 'query', label: 'Query', placeholder: '//foo' },
|
{ type: 'text', name: 'query', label: 'Query', placeholder: '//foo' },
|
||||||
],
|
],
|
||||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const result = (args.values.result || RETURN_FIRST) as XPathResult;
|
||||||
const doc: any = new DOMParser().parseFromString(String(args.values.input), 'text/xml');
|
const join = args.values.join == null ? null : String(args.values.join);
|
||||||
const result = xpath.select(String(args.values.query), doc, false);
|
return filterXPath(String(args.values.input), String(args.values.query), result, join);
|
||||||
if (Array.isArray(result)) {
|
|
||||||
return String(result.map((c) => String(c.firstChild))[0] ?? '');
|
|
||||||
} else if (result instanceof Node) {
|
|
||||||
return String(result.firstChild);
|
|
||||||
} else {
|
|
||||||
return String(result);
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -36,3 +59,26 @@ export const plugin: PluginDefinition = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type XPathResult = 'first' | 'join' | 'all';
|
||||||
|
export function filterXPath(
|
||||||
|
body: string,
|
||||||
|
path: string,
|
||||||
|
result: XPathResult,
|
||||||
|
join: string | null,
|
||||||
|
): string {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const doc: any = new DOMParser().parseFromString(body, 'text/xml');
|
||||||
|
const items = xpath.select(path, doc, false);
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return String(items);
|
||||||
|
} else if (!Array.isArray(items) || result === 'first') {
|
||||||
|
return items[0] != null ? String(items[0].firstChild ?? '') : '';
|
||||||
|
} else if (result === 'join') {
|
||||||
|
return items.map((item) => String(item.firstChild ?? '')).join(join ?? '');
|
||||||
|
} else {
|
||||||
|
// Not sure what cases this happens in (?)
|
||||||
|
return String(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -745,5 +745,47 @@ export const plugin: PluginDefinition = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'triangle',
|
||||||
|
dark: true,
|
||||||
|
label: 'Triangle',
|
||||||
|
base: {
|
||||||
|
surface: 'rgb(0,0,0)',
|
||||||
|
surfaceHighlight: 'rgb(21,21,21)',
|
||||||
|
surfaceActive: 'rgb(31,31,31)',
|
||||||
|
text: 'rgb(237,237,237)',
|
||||||
|
textSubtle: 'rgb(161,161,161)',
|
||||||
|
textSubtlest: 'rgb(115,115,115)',
|
||||||
|
border: 'rgb(31,31,31)',
|
||||||
|
primary: 'rgb(196,114,251)',
|
||||||
|
secondary: 'rgb(161,161,161)',
|
||||||
|
info: 'rgb(71,168,255)',
|
||||||
|
success: 'rgb(0,202,81)',
|
||||||
|
notice: 'rgb(255,175,0)',
|
||||||
|
warning: '#FF4C8D',
|
||||||
|
danger: '#fd495a',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
editor: {
|
||||||
|
danger: '#FF4C8D',
|
||||||
|
warning: '#fd495a',
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
surface: 'rgb(10,10,10)',
|
||||||
|
border: 'rgb(31,31,31)',
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
border: 'rgb(31,31,31)',
|
||||||
|
},
|
||||||
|
responsePane: {
|
||||||
|
surface: 'rgb(10,10,10)',
|
||||||
|
border: 'rgb(31,31,31)',
|
||||||
|
},
|
||||||
|
appHeader: {
|
||||||
|
surface: 'rgb(10,10,10)',
|
||||||
|
border: 'rgb(31,31,31)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const { readdirSync, cpSync } = require('node:fs');
|
const { readdirSync, cpSync } = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { execSync } = require('node:child_process');
|
|
||||||
|
|
||||||
const pluginsDir = path.join(__dirname, '..', 'plugins');
|
const pluginsDir = path.join(__dirname, '..', 'plugins');
|
||||||
|
|
||||||
@@ -9,7 +8,6 @@ console.log('Copying Yaak plugins to', pluginsDir);
|
|||||||
for (const name of readdirSync(pluginsDir)) {
|
for (const name of readdirSync(pluginsDir)) {
|
||||||
const dir = path.join(pluginsDir, name);
|
const dir = path.join(pluginsDir, name);
|
||||||
if (name.startsWith('.')) continue;
|
if (name.startsWith('.')) continue;
|
||||||
execSync('npm run build', { cwd: dir });
|
|
||||||
const destDir = path.join(__dirname, '../src-tauri/vendored/plugins/', name);
|
const destDir = path.join(__dirname, '../src-tauri/vendored/plugins/', name);
|
||||||
console.log(`Copying ${name} to ${destDir}`);
|
console.log(`Copying ${name} to ${destDir}`);
|
||||||
cpSync(path.join(dir, 'package.json'), path.join(destDir, 'package.json'));
|
cpSync(path.join(dir, 'package.json'), path.join(destDir, 'package.json'));
|
||||||
|
|||||||
339
src-tauri/Cargo.lock
generated
339
src-tauri/Cargo.lock
generated
@@ -2,15 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "addr2line"
|
|
||||||
version = "0.24.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
|
||||||
dependencies = [
|
|
||||||
"gimli",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -85,12 +76,6 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android-tzdata"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_log-sys"
|
name = "android_log-sys"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -428,21 +413,6 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backtrace"
|
|
||||||
version = "0.3.75"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
|
||||||
dependencies = [
|
|
||||||
"addr2line",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"miniz_oxide",
|
|
||||||
"object",
|
|
||||||
"rustc-demangle",
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base32"
|
name = "base32"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -717,7 +687,7 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -816,17 +786,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.41"
|
version = "0.4.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1290,7 +1259,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2002,12 +1971,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gimli"
|
|
||||||
version = "0.31.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gio"
|
name = "gio"
|
||||||
version = "0.18.4"
|
version = "0.18.4"
|
||||||
@@ -2389,9 +2352,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.14"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2405,7 +2368,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -2827,7 +2790,7 @@ dependencies = [
|
|||||||
"dbus-secret-service",
|
"dbus-secret-service",
|
||||||
"log",
|
"log",
|
||||||
"security-framework 2.11.1",
|
"security-framework 2.11.1",
|
||||||
"security-framework 3.2.0",
|
"security-framework 3.5.1",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3046,9 +3009,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
@@ -3205,7 +3168,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3628,15 +3591,6 @@ dependencies = [
|
|||||||
"objc2-security",
|
"objc2-security",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "object"
|
|
||||||
version = "0.36.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -3782,7 +3736,7 @@ dependencies = [
|
|||||||
"objc2-osa-kit",
|
"objc2-osa-kit",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4334,8 +4288,8 @@ dependencies = [
|
|||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -4356,7 +4310,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -4371,7 +4325,7 @@ dependencies = [
|
|||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
@@ -4552,7 +4506,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.16",
|
"getrandom 0.2.16",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4766,12 +4720,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.24"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -4815,9 +4763,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.27"
|
version = "0.23.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
"ring",
|
||||||
@@ -4836,7 +4784,7 @@ dependencies = [
|
|||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework 3.2.0",
|
"security-framework 3.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4851,9 +4799,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-platform-verifier"
|
name = "rustls-platform-verifier"
|
||||||
version = "0.6.0"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eda84358ed17f1f354cf4b1909ad346e6c7bc2513e8c40eb08e0157aa13a9070"
|
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -4864,10 +4812,10 @@ dependencies = [
|
|||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"rustls-platform-verifier-android",
|
"rustls-platform-verifier-android",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"security-framework 3.2.0",
|
"security-framework 3.5.1",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4878,9 +4826,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.3"
|
version = "0.103.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@@ -4981,7 +4929,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5015,9 +4963,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.2.0"
|
version = "3.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
@@ -5028,9 +4976,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework-sys"
|
name = "security-framework-sys"
|
||||||
version = "2.14.0"
|
version = "2.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -5065,9 +5013,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@@ -5107,18 +5055,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.226"
|
version = "1.0.228"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -5138,14 +5086,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.140"
|
version = "1.0.145"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5392,6 +5341,16 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "softbuffer"
|
name = "softbuffer"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -5587,9 +5546,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tao"
|
name = "tao"
|
||||||
version = "0.34.3"
|
version = "0.34.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7"
|
checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"block2 0.6.1",
|
"block2 0.6.1",
|
||||||
@@ -5661,9 +5620,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.8.5"
|
version = "2.9.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c"
|
checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5701,11 +5660,10 @@ dependencies = [
|
|||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
"tauri-runtime-wry",
|
"tauri-runtime-wry",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"window-vibrancy",
|
"window-vibrancy",
|
||||||
@@ -5714,9 +5672,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "2.4.1"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f"
|
checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@@ -5736,9 +5694,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
|
checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -5754,7 +5712,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"syn 2.0.101",
|
"syn 2.0.101",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"time",
|
"time",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -5763,9 +5721,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
|
checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -5777,9 +5735,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin"
|
name = "tauri-plugin"
|
||||||
version = "2.4.0"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
|
checksum = "076c78a474a7247c90cad0b6e87e593c4c620ed4efdb79cbe0214f0021f6c39d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -5804,7 +5762,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5821,7 +5779,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"windows-registry",
|
"windows-registry",
|
||||||
@@ -5830,9 +5788,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.4.0"
|
version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e"
|
checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -5842,15 +5800,15 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.4.2"
|
version = "2.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
|
checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -5863,7 +5821,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"toml 0.9.5",
|
"toml 0.9.5",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@@ -5886,7 +5844,7 @@ dependencies = [
|
|||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5906,7 +5864,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows",
|
||||||
"zbus",
|
"zbus",
|
||||||
@@ -5927,14 +5885,14 @@ dependencies = [
|
|||||||
"sys-locale",
|
"sys-locale",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-shell"
|
name = "tauri-plugin-shell"
|
||||||
version = "2.3.1"
|
version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54777d0c0d8add34eea3ced84378619ef5b97996bd967d3038c668feefd21071"
|
checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -5947,7 +5905,7 @@ dependencies = [
|
|||||||
"shared_child",
|
"shared_child",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5961,7 +5919,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin-deep-link",
|
"tauri-plugin-deep-link",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
"zbus",
|
"zbus",
|
||||||
@@ -5991,7 +5949,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
@@ -6011,14 +5969,14 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.8.0"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
|
checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
@@ -6032,7 +5990,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
@@ -6041,9 +5999,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "2.8.1"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
|
checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@@ -6068,9 +6026,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
|
checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -6096,7 +6054,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"toml 0.9.5",
|
"toml 0.9.5",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
@@ -6159,11 +6117,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6179,9 +6137,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.12"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -6268,27 +6226,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.45.1"
|
version = "1.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2 0.6.1",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -6478,7 +6435,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
@@ -6614,7 +6571,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6645,21 +6602,21 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs"
|
name = "ts-rs"
|
||||||
version = "11.0.1"
|
version = "11.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
|
checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"ts-rs-macros",
|
"ts-rs-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ts-rs-macros"
|
name = "ts-rs-macros"
|
||||||
version = "11.0.1"
|
version = "11.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
|
checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -6682,7 +6639,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7204,7 +7161,7 @@ version = "0.38.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
|
checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
]
|
]
|
||||||
@@ -7270,7 +7227,7 @@ dependencies = [
|
|||||||
"windows-collections",
|
"windows-collections",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
"windows-future",
|
"windows-future",
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7291,7 +7248,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
"windows-result",
|
"windows-result",
|
||||||
"windows-strings",
|
"windows-strings",
|
||||||
]
|
]
|
||||||
@@ -7303,7 +7260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core",
|
"windows-core",
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
"windows-threading",
|
"windows-threading",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7335,6 +7292,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-numerics"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -7342,7 +7305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core",
|
"windows-core",
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7351,7 +7314,7 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
|
checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
"windows-result",
|
"windows-result",
|
||||||
"windows-strings",
|
"windows-strings",
|
||||||
]
|
]
|
||||||
@@ -7362,7 +7325,7 @@ version = "0.3.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7371,7 +7334,7 @@ version = "0.4.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7410,6 +7373,15 @@ dependencies = [
|
|||||||
"windows-targets 0.53.2",
|
"windows-targets 0.53.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -7478,7 +7450,7 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7487,7 +7459,7 @@ version = "0.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c"
|
checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link 0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7718,7 +7690,7 @@ dependencies = [
|
|||||||
"os_pipe",
|
"os_pipe",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tree_magic_mini",
|
"tree_magic_mini",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
@@ -7734,9 +7706,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.53.3"
|
version = "0.53.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90"
|
checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"block2 0.6.1",
|
"block2 0.6.1",
|
||||||
@@ -7766,7 +7738,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"soup3",
|
"soup3",
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
@@ -7865,7 +7837,7 @@ dependencies = [
|
|||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
@@ -7894,7 +7866,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"tauri",
|
"tauri",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7909,7 +7881,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7921,7 +7893,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7937,7 +7909,7 @@ dependencies = [
|
|||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
"yaak-sync",
|
"yaak-sync",
|
||||||
@@ -7973,9 +7945,18 @@ dependencies = [
|
|||||||
name = "yaak-http"
|
name = "yaak-http"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"hyper-util",
|
||||||
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
|
"reqwest",
|
||||||
|
"reqwest_cookie_store",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
|
"serde",
|
||||||
|
"tauri",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
]
|
]
|
||||||
@@ -7991,7 +7972,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"yaak-common",
|
"yaak-common",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
@@ -8030,9 +8011,9 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
|
"yaak-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8057,7 +8038,7 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
@@ -8091,7 +8072,7 @@ dependencies = [
|
|||||||
"sha1",
|
"sha1",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
@@ -8106,7 +8087,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -8124,7 +8105,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"yaak-http",
|
"yaak-http",
|
||||||
@@ -8345,7 +8326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "aed5f10c571472911e37d8f7601a8dfba52b4f7f73a344015291b82ab292faf6"
|
checksum = "aed5f10c571472911e37d8f7601a8dfba52b4f7f73a344015291b82ab292faf6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.17",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ updater = []
|
|||||||
license = ["yaak-license"]
|
license = ["yaak-license"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.4.1", features = [] }
|
tauri-build = { version = "2.5.0", features = [] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
||||||
@@ -48,7 +48,7 @@ chrono = { workspace = true, features = ["serde"] }
|
|||||||
cookie = "0.18.1"
|
cookie = "0.18.1"
|
||||||
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" }
|
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" }
|
||||||
http = { version = "1.2.0", default-features = false }
|
http = { version = "1.2.0", default-features = false }
|
||||||
log = "0.4.27"
|
log = { workspace = true }
|
||||||
md5 = "0.8.0"
|
md5 = "0.8.0"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
@@ -89,23 +89,24 @@ yaak-templates = { workspace = true }
|
|||||||
yaak-ws = { path = "yaak-ws" }
|
yaak-ws = { path = "yaak-ws" }
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.42"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
keyring = "3.6.3"
|
keyring = "3.6.3"
|
||||||
reqwest = "0.12.20"
|
reqwest = "0.12.20"
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
rustls = { version = "0.23.27", default-features = false }
|
rustls = { version = "0.23.34", default-features = false }
|
||||||
rustls-platform-verifier = "0.6.0"
|
rustls-platform-verifier = "0.6.2"
|
||||||
serde = "1.0.219"
|
serde = "1.0.228"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.145"
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
tauri = "2.8.5"
|
log = "0.4.28"
|
||||||
tauri-plugin = "2.4.0"
|
tauri = "2.9.2"
|
||||||
tauri-plugin-dialog = "2.4.0"
|
tauri-plugin = "2.5.1"
|
||||||
tauri-plugin-shell = "2.3.1"
|
tauri-plugin-dialog = "2.4.2"
|
||||||
thiserror = "2.0.12"
|
tauri-plugin-shell = "2.3.3"
|
||||||
tokio = "1.45.1"
|
thiserror = "2.0.17"
|
||||||
ts-rs = "11.0.1"
|
tokio = "1.48.0"
|
||||||
|
ts-rs = "11.1.0"
|
||||||
yaak-common = { path = "yaak-common" }
|
yaak-common = { path = "yaak-common" }
|
||||||
yaak-crypto = { path = "yaak-crypto" }
|
yaak-crypto = { path = "yaak-crypto" }
|
||||||
yaak-fonts = { path = "yaak-fonts" }
|
yaak-fonts = { path = "yaak-fonts" }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::error::Result;
|
|||||||
use tauri::{command, AppHandle, Manager, Runtime, State, WebviewWindow};
|
use tauri::{command, AppHandle, Manager, Runtime, State, WebviewWindow};
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
|
||||||
use yaak_crypto::manager::EncryptionManagerExt;
|
use yaak_crypto::manager::EncryptionManagerExt;
|
||||||
use yaak_plugins::events::{GetThemesResponse, PluginWindowContext};
|
use yaak_plugins::events::{GetThemesResponse, PluginContext};
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
use yaak_plugins::native_template_functions::{
|
use yaak_plugins::native_template_functions::{
|
||||||
decrypt_secure_template_function, encrypt_secure_template_function,
|
decrypt_secure_template_function, encrypt_secure_template_function,
|
||||||
@@ -28,8 +28,8 @@ pub(crate) async fn cmd_decrypt_template<R: Runtime>(
|
|||||||
template: &str,
|
template: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let app_handle = window.app_handle();
|
let app_handle = window.app_handle();
|
||||||
let window_context = &PluginWindowContext::new(&window);
|
let plugin_context = &PluginContext::new(&window);
|
||||||
Ok(decrypt_secure_template_function(&app_handle, window_context, template)?)
|
Ok(decrypt_secure_template_function(&app_handle, plugin_context, template)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
@@ -38,8 +38,8 @@ pub(crate) async fn cmd_secure_template<R: Runtime>(
|
|||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
template: &str,
|
template: &str,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let window_context = &PluginWindowContext::new(&window);
|
let plugin_context = &PluginContext::new(&window);
|
||||||
Ok(encrypt_secure_template_function(&app_handle, window_context, template)?)
|
Ok(encrypt_secure_template_function(&app_handle, plugin_context, template)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use log::debug;
|
use mime_guess::{mime, Mime};
|
||||||
use mime_guess::{Mime, mime};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
@@ -7,14 +6,8 @@ use tokio::fs;
|
|||||||
pub async fn read_response_body(body_path: impl AsRef<Path>, content_type: &str) -> Option<String> {
|
pub async fn read_response_body(body_path: impl AsRef<Path>, content_type: &str) -> Option<String> {
|
||||||
let body = fs::read(body_path).await.ok()?;
|
let body = fs::read(body_path).await.ok()?;
|
||||||
let body_charset = parse_charset(content_type).unwrap_or("utf-8".to_string());
|
let body_charset = parse_charset(content_type).unwrap_or("utf-8".to_string());
|
||||||
debug!("body_charset: {}", body_charset);
|
|
||||||
if let Some(decoder) = charset::Charset::for_label(body_charset.as_bytes()) {
|
if let Some(decoder) = charset::Charset::for_label(body_charset.as_bytes()) {
|
||||||
debug!("Using decoder for charset: {}", body_charset);
|
let (cow, _real_encoding, _exist_replace) = decoder.decode(&body);
|
||||||
let (cow, real_encoding, exist_replace) = decoder.decode(&body);
|
|
||||||
debug!(
|
|
||||||
"Decoded body with charset: {}, real_encoding: {:?}, exist_replace: {}",
|
|
||||||
body_charset, real_encoding, exist_replace
|
|
||||||
);
|
|
||||||
return cow.into_owned().into();
|
return cow.into_owned().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ pub enum Error {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CryptoError(#[from] yaak_crypto::error::Error),
|
CryptoError(#[from] yaak_crypto::error::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
HttpError(#[from] yaak_http::error::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
GitError(#[from] yaak_git::error::Error),
|
GitError(#[from] yaak_git::error::Error),
|
||||||
|
|
||||||
@@ -35,6 +38,9 @@ pub enum Error {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CommonError(#[from] yaak_common::error::Error),
|
CommonError(#[from] yaak_common::error::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
ClipboardError(#[from] tauri_plugin_clipboard_manager::Error),
|
||||||
|
|
||||||
#[error("Updater error: {0}")]
|
#[error("Updater error: {0}")]
|
||||||
UpdaterError(#[from] tauri_plugin_updater::Error),
|
UpdaterError(#[from] tauri_plugin_updater::Error),
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use tauri::{Manager, Runtime, WebviewWindow};
|
|||||||
use yaak_grpc::{KeyAndValueRef, MetadataMap};
|
use yaak_grpc::{KeyAndValueRef, MetadataMap};
|
||||||
use yaak_models::models::GrpcRequest;
|
use yaak_models::models::GrpcRequest;
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_plugins::events::{CallHttpAuthenticationRequest, HttpHeader};
|
use yaak_plugins::events::{CallHttpAuthenticationRequest, HttpHeader, PluginContext};
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
|
|
||||||
pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String> {
|
pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String> {
|
||||||
@@ -81,7 +81,12 @@ pub(crate) async fn build_metadata<R: Runtime>(
|
|||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let plugin_result = plugin_manager
|
let plugin_result = plugin_manager
|
||||||
.call_http_authentication(&window, &authentication_type, plugin_req)
|
.call_http_authentication(
|
||||||
|
&window,
|
||||||
|
&authentication_type,
|
||||||
|
plugin_req,
|
||||||
|
&PluginContext::new(window),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
for header in plugin_result.set_headers.unwrap_or_default() {
|
for header in plugin_result.set_headers.unwrap_or_default() {
|
||||||
metadata.insert(header.name, header.value);
|
metadata.insert(header.name, header.value);
|
||||||
|
|||||||
@@ -1,48 +1,74 @@
|
|||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use log::debug;
|
||||||
|
use std::sync::OnceLock;
|
||||||
use tauri::{AppHandle, Runtime};
|
use tauri::{AppHandle, Runtime};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
|
|
||||||
const NAMESPACE: &str = "analytics";
|
const NAMESPACE: &str = "analytics";
|
||||||
const NUM_LAUNCHES_KEY: &str = "num_launches";
|
const NUM_LAUNCHES_KEY: &str = "num_launches";
|
||||||
|
const LAST_VERSION_KEY: &str = "last_tracked_version";
|
||||||
|
const PREV_VERSION_KEY: &str = "last_tracked_version_prev";
|
||||||
|
const VERSION_SINCE_KEY: &str = "last_tracked_version_since";
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct LaunchEventInfo {
|
pub struct LaunchEventInfo {
|
||||||
pub current_version: String,
|
pub current_version: String,
|
||||||
pub previous_version: String,
|
pub previous_version: String,
|
||||||
pub launched_after_update: bool,
|
pub launched_after_update: bool,
|
||||||
|
pub version_since: NaiveDateTime,
|
||||||
|
pub user_since: NaiveDateTime,
|
||||||
pub num_launches: i32,
|
pub num_launches: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn store_launch_history<R: Runtime>(app_handle: &AppHandle<R>) -> LaunchEventInfo {
|
static LAUNCH_INFO: OnceLock<LaunchEventInfo> = OnceLock::new();
|
||||||
let last_tracked_version_key = "last_tracked_version";
|
|
||||||
|
|
||||||
let mut info = LaunchEventInfo::default();
|
pub fn get_or_upsert_launch_info<R: Runtime>(app_handle: &AppHandle<R>) -> &LaunchEventInfo {
|
||||||
|
LAUNCH_INFO.get_or_init(|| {
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
|
let mut info = LaunchEventInfo {
|
||||||
|
version_since: app_handle.db().get_key_value_dte(NAMESPACE, VERSION_SINCE_KEY, now),
|
||||||
|
current_version: app_handle.package_info().version.to_string(),
|
||||||
|
user_since: app_handle.db().get_settings().created_at,
|
||||||
|
num_launches: app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0) + 1,
|
||||||
|
|
||||||
info.num_launches = get_num_launches(app_handle).await + 1;
|
// The rest will be set below
|
||||||
info.current_version = app_handle.package_info().version.to_string();
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.with_tx(|tx| {
|
.with_tx(|tx| {
|
||||||
info.previous_version =
|
// Load the previously tracked version
|
||||||
tx.get_key_value_string(NAMESPACE, last_tracked_version_key, "");
|
let curr_db = tx.get_key_value_str(NAMESPACE, LAST_VERSION_KEY, "");
|
||||||
|
let prev_db = tx.get_key_value_str(NAMESPACE, PREV_VERSION_KEY, "");
|
||||||
|
|
||||||
if !info.previous_version.is_empty() {
|
// We just updated if the app version is different from the last tracked version we stored
|
||||||
info.launched_after_update = info.current_version != info.previous_version;
|
if !curr_db.is_empty() && info.current_version != curr_db {
|
||||||
};
|
info.launched_after_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Update key values
|
// If we just updated, track the previous version as the "previous" current version
|
||||||
|
if info.launched_after_update {
|
||||||
|
info.previous_version = curr_db.clone();
|
||||||
|
info.version_since = now;
|
||||||
|
} else {
|
||||||
|
info.previous_version = prev_db.clone();
|
||||||
|
}
|
||||||
|
|
||||||
let source = &UpdateSource::Background;
|
// Rotate stored versions: move previous into the "prev" slot before overwriting
|
||||||
let version = info.current_version.as_str();
|
let source = &UpdateSource::Background;
|
||||||
tx.set_key_value_string(NAMESPACE, last_tracked_version_key, version, source);
|
|
||||||
tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
info
|
tx.set_key_value_str(NAMESPACE, PREV_VERSION_KEY, &info.previous_version, source);
|
||||||
}
|
tx.set_key_value_str(NAMESPACE, LAST_VERSION_KEY, &info.current_version, source);
|
||||||
|
tx.set_key_value_dte(NAMESPACE, VERSION_SINCE_KEY, info.version_since, source);
|
||||||
pub async fn get_num_launches<R: Runtime>(app_handle: &AppHandle<R>) -> i32 {
|
tx.set_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, source);
|
||||||
app_handle.db().get_key_value_int(NAMESPACE, NUM_LAUNCHES_KEY, 0)
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
debug!("Initialized launch info");
|
||||||
|
|
||||||
|
info
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use http::header::{ACCEPT, USER_AGENT};
|
|||||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::{Method, Response};
|
||||||
use reqwest::{Method, NoProxy, Response};
|
use reqwest::{Url, multipart};
|
||||||
use reqwest::{Proxy, Url, multipart};
|
use reqwest_cookie_store::{CookieStore, CookieStoreMutex};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -21,6 +21,10 @@ use tokio::fs::{File, create_dir_all};
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::sync::watch::Receiver;
|
use tokio::sync::watch::Receiver;
|
||||||
use tokio::sync::{Mutex, oneshot};
|
use tokio::sync::{Mutex, oneshot};
|
||||||
|
use yaak_http::client::{
|
||||||
|
HttpConnectionOptions, HttpConnectionProxySetting, HttpConnectionProxySettingAuth,
|
||||||
|
};
|
||||||
|
use yaak_http::manager::HttpConnectionManager;
|
||||||
use yaak_models::models::{
|
use yaak_models::models::{
|
||||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
||||||
HttpResponseState, ProxySetting, ProxySettingAuth,
|
HttpResponseState, ProxySetting, ProxySettingAuth,
|
||||||
@@ -28,7 +32,7 @@ use yaak_models::models::{
|
|||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
CallHttpAuthenticationRequest, HttpHeader, PluginWindowContext, RenderPurpose,
|
CallHttpAuthenticationRequest, HttpHeader, PluginContext, RenderPurpose,
|
||||||
};
|
};
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
@@ -41,9 +45,31 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
environment: Option<Environment>,
|
environment: Option<Environment>,
|
||||||
cookie_jar: Option<CookieJar>,
|
cookie_jar: Option<CookieJar>,
|
||||||
cancelled_rx: &mut Receiver<bool>,
|
cancelled_rx: &mut Receiver<bool>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
send_http_request_with_context(
|
||||||
|
window,
|
||||||
|
unrendered_request,
|
||||||
|
og_response,
|
||||||
|
environment,
|
||||||
|
cookie_jar,
|
||||||
|
cancelled_rx,
|
||||||
|
&PluginContext::new(window),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_http_request_with_context<R: Runtime>(
|
||||||
|
window: &WebviewWindow<R>,
|
||||||
|
unrendered_request: &HttpRequest,
|
||||||
|
og_response: &HttpResponse,
|
||||||
|
environment: Option<Environment>,
|
||||||
|
cookie_jar: Option<CookieJar>,
|
||||||
|
cancelled_rx: &mut Receiver<bool>,
|
||||||
|
plugin_context: &PluginContext,
|
||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
let app_handle = window.app_handle().clone();
|
let app_handle = window.app_handle().clone();
|
||||||
let plugin_manager = app_handle.state::<PluginManager>();
|
let plugin_manager = app_handle.state::<PluginManager>();
|
||||||
|
let connection_manager = app_handle.state::<HttpConnectionManager>();
|
||||||
let settings = window.db().get_settings();
|
let settings = window.db().get_settings();
|
||||||
let workspace = window.db().get_workspace(&unrendered_request.workspace_id)?;
|
let workspace = window.db().get_workspace(&unrendered_request.workspace_id)?;
|
||||||
let environment_id = environment.map(|e| e.id);
|
let environment_id = environment.map(|e| e.id);
|
||||||
@@ -71,11 +97,7 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cb = PluginTemplateCallback::new(
|
let cb = PluginTemplateCallback::new(window.app_handle(), &plugin_context, RenderPurpose::Send);
|
||||||
window.app_handle(),
|
|
||||||
&PluginWindowContext::new(window),
|
|
||||||
RenderPurpose::Send,
|
|
||||||
);
|
|
||||||
|
|
||||||
let opt = RenderOptions {
|
let opt = RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
@@ -101,64 +123,33 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
}
|
}
|
||||||
debug!("Sending request to {} {url_string}", request.method);
|
debug!("Sending request to {} {url_string}", request.method);
|
||||||
|
|
||||||
let mut client_builder = reqwest::Client::builder()
|
let proxy_setting = match settings.proxy {
|
||||||
.redirect(match workspace.setting_follow_redirects {
|
None => HttpConnectionProxySetting::System,
|
||||||
true => Policy::limited(10), // TODO: Handle redirects natively
|
Some(ProxySetting::Disabled) => HttpConnectionProxySetting::Disabled,
|
||||||
false => Policy::none(),
|
|
||||||
})
|
|
||||||
.connection_verbose(true)
|
|
||||||
.gzip(true)
|
|
||||||
.brotli(true)
|
|
||||||
.deflate(true)
|
|
||||||
.referer(false)
|
|
||||||
.tls_info(true);
|
|
||||||
|
|
||||||
let tls_config = yaak_http::tls::get_config(workspace.setting_validate_certificates, true);
|
|
||||||
client_builder = client_builder.use_preconfigured_tls(tls_config);
|
|
||||||
|
|
||||||
match settings.proxy {
|
|
||||||
Some(ProxySetting::Disabled) => client_builder = client_builder.no_proxy(),
|
|
||||||
Some(ProxySetting::Enabled {
|
Some(ProxySetting::Enabled {
|
||||||
http,
|
http,
|
||||||
https,
|
https,
|
||||||
auth,
|
auth,
|
||||||
disabled,
|
|
||||||
bypass,
|
bypass,
|
||||||
}) if !disabled => {
|
disabled,
|
||||||
debug!("Using proxy http={http} https={https} bypass={bypass}");
|
}) => {
|
||||||
if !http.is_empty() {
|
if disabled {
|
||||||
match Proxy::http(http) {
|
HttpConnectionProxySetting::System
|
||||||
Ok(mut proxy) => {
|
} else {
|
||||||
if let Some(ProxySettingAuth { user, password }) = auth.clone() {
|
HttpConnectionProxySetting::Enabled {
|
||||||
debug!("Using http proxy auth");
|
http,
|
||||||
proxy = proxy.basic_auth(user.as_str(), password.as_str());
|
https,
|
||||||
|
bypass,
|
||||||
|
auth: match auth {
|
||||||
|
None => None,
|
||||||
|
Some(ProxySettingAuth { user, password }) => {
|
||||||
|
Some(HttpConnectionProxySettingAuth { user, password })
|
||||||
}
|
}
|
||||||
proxy = proxy.no_proxy(NoProxy::from_string(&bypass));
|
},
|
||||||
client_builder = client_builder.proxy(proxy);
|
}
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to apply http proxy {e:?}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if !https.is_empty() {
|
|
||||||
match Proxy::https(https) {
|
|
||||||
Ok(mut proxy) => {
|
|
||||||
if let Some(ProxySettingAuth { user, password }) = auth {
|
|
||||||
debug!("Using https proxy auth");
|
|
||||||
proxy = proxy.basic_auth(user.as_str(), password.as_str());
|
|
||||||
}
|
|
||||||
proxy = proxy.no_proxy(NoProxy::from_string(&bypass));
|
|
||||||
client_builder = client_builder.proxy(proxy);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to apply https proxy {e:?}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {} // Nothing to do for this one, as it is the default
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Add cookie store if specified
|
// Add cookie store if specified
|
||||||
let maybe_cookie_manager = match cookie_jar.clone() {
|
let maybe_cookie_manager = match cookie_jar.clone() {
|
||||||
@@ -177,23 +168,33 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
.map(|c| Ok(c))
|
.map(|c| Ok(c))
|
||||||
.collect::<Vec<Result<_>>>();
|
.collect::<Vec<Result<_>>>();
|
||||||
|
|
||||||
let store = reqwest_cookie_store::CookieStore::from_cookies(cookies, true)?;
|
let cookie_store = CookieStore::from_cookies(cookies, true)?;
|
||||||
let cookie_store = reqwest_cookie_store::CookieStoreMutex::new(store);
|
let cookie_store = CookieStoreMutex::new(cookie_store);
|
||||||
let cookie_store = Arc::new(cookie_store);
|
let cookie_store = Arc::new(cookie_store);
|
||||||
client_builder = client_builder.cookie_provider(Arc::clone(&cookie_store));
|
let cookie_provider = Arc::clone(&cookie_store);
|
||||||
|
Some((cookie_provider, cj))
|
||||||
Some((cookie_store, cj))
|
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if workspace.setting_request_timeout > 0 {
|
let client = connection_manager
|
||||||
client_builder = client_builder.timeout(Duration::from_millis(
|
.get_client(
|
||||||
workspace.setting_request_timeout.unsigned_abs() as u64,
|
&plugin_context.id,
|
||||||
));
|
&HttpConnectionOptions {
|
||||||
}
|
follow_redirects: workspace.setting_follow_redirects,
|
||||||
|
validate_certificates: workspace.setting_validate_certificates,
|
||||||
let client = client_builder.build()?;
|
proxy: proxy_setting,
|
||||||
|
cookie_provider: maybe_cookie_manager.as_ref().map(|(p, _)| Arc::clone(&p)),
|
||||||
|
timeout: if workspace.setting_request_timeout > 0 {
|
||||||
|
Some(Duration::from_millis(
|
||||||
|
workspace.setting_request_timeout.unsigned_abs() as u64
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Render query parameters
|
// Render query parameters
|
||||||
let mut query_params = Vec::new();
|
let mut query_params = Vec::new();
|
||||||
@@ -467,8 +468,9 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let auth_result =
|
let auth_result = plugin_manager
|
||||||
plugin_manager.call_http_authentication(&window, &authentication_type, req).await;
|
.call_http_authentication(&window, &authentication_type, req, plugin_context)
|
||||||
|
.await;
|
||||||
let plugin_result = match auth_result {
|
let plugin_result = match auth_result {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.workspaces
|
.workspaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<Workspace>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<Workspace, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -37,11 +37,12 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.environments
|
.environments
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<Environment>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<Environment, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
v.workspace_id =
|
||||||
|
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||||
match (v.parent_model.as_str(), v.parent_id.clone().as_deref()) {
|
match (v.parent_model.as_str(), v.parent_id.clone().as_deref()) {
|
||||||
("folder", Some(parent_id)) => {
|
("folder", Some(parent_id)) => {
|
||||||
v.parent_id = Some(maybe_gen_id::<Folder>(&parent_id, &mut id_map));
|
v.parent_id = Some(maybe_gen_id::<Folder, R>(window, &parent_id, &mut id_map));
|
||||||
}
|
}
|
||||||
("", _) => {
|
("", _) => {
|
||||||
// Fix any empty ones
|
// Fix any empty ones
|
||||||
@@ -60,9 +61,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.folders
|
.folders
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<Folder>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<Folder, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
v.workspace_id =
|
||||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||||
|
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -71,9 +73,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.http_requests
|
.http_requests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<HttpRequest>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<HttpRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
v.workspace_id =
|
||||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||||
|
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -82,9 +85,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.grpc_requests
|
.grpc_requests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<GrpcRequest>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<GrpcRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
v.workspace_id =
|
||||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||||
|
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -93,9 +97,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
|||||||
.websocket_requests
|
.websocket_requests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut v| {
|
.map(|mut v| {
|
||||||
v.id = maybe_gen_id::<WebsocketRequest>(v.id.as_str(), &mut id_map);
|
v.id = maybe_gen_id::<WebsocketRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
v.workspace_id =
|
||||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||||
|
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
extern crate core;
|
extern crate core;
|
||||||
use crate::encoding::read_response_body;
|
use crate::encoding::read_response_body;
|
||||||
use crate::error::Error::GenericError;
|
use crate::error::Error::GenericError;
|
||||||
|
use crate::error::Result;
|
||||||
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
|
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
|
||||||
use crate::http_request::{resolve_http_request, send_http_request};
|
use crate::http_request::{resolve_http_request, send_http_request};
|
||||||
use crate::import::import_data;
|
use crate::import::import_data;
|
||||||
@@ -22,7 +23,7 @@ use tauri::{Listener, Runtime};
|
|||||||
use tauri::{Manager, WindowEvent};
|
use tauri::{Manager, WindowEvent};
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||||
use tauri_plugin_log::{Builder, Target, TargetKind};
|
use tauri_plugin_log::{Builder, Target, TargetKind, log};
|
||||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
@@ -41,15 +42,16 @@ use yaak_plugins::events::{
|
|||||||
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
|
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
|
||||||
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
|
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
|
||||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
|
GetHttpRequestActionsResponse, GetTemplateFunctionConfigResponse,
|
||||||
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest,
|
GetTemplateFunctionSummaryResponse, InternalEvent, InternalEventPayload, JsonPrimitive,
|
||||||
|
PluginContext, RenderPurpose, ShowToastRequest,
|
||||||
};
|
};
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
use yaak_sse::sse::ServerSentEvent;
|
use yaak_sse::sse::ServerSentEvent;
|
||||||
use yaak_templates::format::format_json;
|
use yaak_templates::format_json::format_json;
|
||||||
use yaak_templates::{Tokens, transform_args, RenderOptions, RenderErrorBehavior};
|
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
@@ -101,7 +103,7 @@ async fn cmd_template_tokens_to_string<R: Runtime>(
|
|||||||
) -> YaakResult<String> {
|
) -> YaakResult<String> {
|
||||||
let cb = PluginTemplateCallback::new(
|
let cb = PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Preview,
|
RenderPurpose::Preview,
|
||||||
);
|
);
|
||||||
let new_tokens = transform_args(tokens, &cb)?;
|
let new_tokens = transform_args(tokens, &cb)?;
|
||||||
@@ -115,6 +117,7 @@ async fn cmd_render_template<R: Runtime>(
|
|||||||
template: &str,
|
template: &str,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
environment_id: Option<&str>,
|
environment_id: Option<&str>,
|
||||||
|
purpose: Option<RenderPurpose>,
|
||||||
) -> YaakResult<String> {
|
) -> YaakResult<String> {
|
||||||
let environment_chain =
|
let environment_chain =
|
||||||
app_handle.db().resolve_environments(workspace_id, None, environment_id)?;
|
app_handle.db().resolve_environments(workspace_id, None, environment_id)?;
|
||||||
@@ -123,8 +126,8 @@ async fn cmd_render_template<R: Runtime>(
|
|||||||
environment_chain,
|
environment_chain,
|
||||||
&PluginTemplateCallback::new(
|
&PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Preview,
|
purpose.unwrap_or(RenderPurpose::Preview),
|
||||||
),
|
),
|
||||||
&RenderOptions {
|
&RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
@@ -167,7 +170,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
|||||||
environment_chain,
|
environment_chain,
|
||||||
&PluginTemplateCallback::new(
|
&PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Send,
|
RenderPurpose::Send,
|
||||||
),
|
),
|
||||||
&RenderOptions {
|
&RenderOptions {
|
||||||
@@ -216,7 +219,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
environment_chain.clone(),
|
environment_chain.clone(),
|
||||||
&PluginTemplateCallback::new(
|
&PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Send,
|
RenderPurpose::Send,
|
||||||
),
|
),
|
||||||
&RenderOptions {
|
&RenderOptions {
|
||||||
@@ -341,7 +344,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
environment_chain,
|
environment_chain,
|
||||||
&PluginTemplateCallback::new(
|
&PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Send,
|
RenderPurpose::Send,
|
||||||
),
|
),
|
||||||
&RenderOptions {
|
&RenderOptions {
|
||||||
@@ -413,7 +416,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
environment_chain,
|
environment_chain,
|
||||||
&PluginTemplateCallback::new(
|
&PluginTemplateCallback::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&PluginWindowContext::new(&window),
|
&PluginContext::new(&window),
|
||||||
RenderPurpose::Send,
|
RenderPurpose::Send,
|
||||||
),
|
),
|
||||||
&RenderOptions {
|
&RenderOptions {
|
||||||
@@ -826,11 +829,40 @@ async fn cmd_grpc_request_actions<R: Runtime>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_template_functions<R: Runtime>(
|
async fn cmd_template_function_summaries<R: Runtime>(
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
) -> YaakResult<Vec<GetTemplateFunctionsResponse>> {
|
) -> YaakResult<Vec<GetTemplateFunctionSummaryResponse>> {
|
||||||
Ok(plugin_manager.get_template_functions(&window).await?)
|
let results = plugin_manager.get_template_function_summaries(&window).await?;
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_template_function_config<R: Runtime>(
|
||||||
|
window: WebviewWindow<R>,
|
||||||
|
plugin_manager: State<'_, PluginManager>,
|
||||||
|
function_name: &str,
|
||||||
|
values: HashMap<String, JsonPrimitive>,
|
||||||
|
model: AnyModel,
|
||||||
|
environment_id: Option<&str>,
|
||||||
|
) -> YaakResult<GetTemplateFunctionConfigResponse> {
|
||||||
|
let (workspace_id, folder_id) = match model.clone() {
|
||||||
|
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::Folder(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::Workspace(m) => (m.id, None),
|
||||||
|
m => {
|
||||||
|
return Err(GenericError(format!(
|
||||||
|
"Unsupported model to call template functions {m:?}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let environment_chain =
|
||||||
|
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
||||||
|
Ok(plugin_manager
|
||||||
|
.get_template_function_config(&window, function_name, environment_chain, values, model.id())
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -848,10 +880,10 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
|||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
request: AnyModel,
|
model: AnyModel,
|
||||||
environment_id: Option<&str>,
|
environment_id: Option<&str>,
|
||||||
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
||||||
let (workspace_id, folder_id) = match request.clone() {
|
let (workspace_id, folder_id) = match model.clone() {
|
||||||
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
@@ -866,7 +898,7 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
|||||||
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
||||||
|
|
||||||
Ok(plugin_manager
|
Ok(plugin_manager
|
||||||
.get_http_authentication_config(&window, environment_chain, auth_name, values, request.id())
|
.get_http_authentication_config(&window, environment_chain, auth_name, values, model.id())
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1006,6 +1038,35 @@ async fn cmd_save_response<R: Runtime>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_send_folder<R: Runtime>(
|
||||||
|
app_handle: AppHandle<R>,
|
||||||
|
window: WebviewWindow<R>,
|
||||||
|
environment_id: Option<String>,
|
||||||
|
cookie_jar_id: Option<String>,
|
||||||
|
folder_id: &str,
|
||||||
|
) -> YaakResult<()> {
|
||||||
|
let requests = app_handle.db().list_http_requests_for_folder_recursive(folder_id)?;
|
||||||
|
for request in requests {
|
||||||
|
let app_handle = app_handle.clone();
|
||||||
|
let window = window.clone();
|
||||||
|
let environment_id = environment_id.clone();
|
||||||
|
let cookie_jar_id = cookie_jar_id.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _ = cmd_send_http_request(
|
||||||
|
app_handle,
|
||||||
|
window,
|
||||||
|
environment_id.as_deref(),
|
||||||
|
cookie_jar_id.as_deref(),
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_send_http_request<R: Runtime>(
|
async fn cmd_send_http_request<R: Runtime>(
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
@@ -1101,7 +1162,7 @@ async fn cmd_install_plugin<R: Runtime>(
|
|||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
) -> YaakResult<Plugin> {
|
) -> YaakResult<Plugin> {
|
||||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &directory).await?;
|
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &directory).await?;
|
||||||
|
|
||||||
Ok(app_handle.db().upsert_plugin(
|
Ok(app_handle.db().upsert_plugin(
|
||||||
&Plugin {
|
&Plugin {
|
||||||
@@ -1117,7 +1178,7 @@ async fn cmd_install_plugin<R: Runtime>(
|
|||||||
async fn cmd_create_grpc_request<R: Runtime>(
|
async fn cmd_create_grpc_request<R: Runtime>(
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
sort_priority: f32,
|
sort_priority: f64,
|
||||||
folder_id: Option<&str>,
|
folder_id: Option<&str>,
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
@@ -1140,7 +1201,7 @@ async fn cmd_reload_plugins<R: Runtime>(
|
|||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
) -> YaakResult<()> {
|
) -> YaakResult<()> {
|
||||||
plugin_manager.initialize_all_plugins(&app_handle, &PluginWindowContext::new(&window)).await?;
|
plugin_manager.initialize_all_plugins(&app_handle, &PluginContext::new(&window)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,7 +1342,6 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::default().build())
|
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
@@ -1291,6 +1351,7 @@ pub fn run() {
|
|||||||
.plugin(yaak_crypto::init())
|
.plugin(yaak_crypto::init())
|
||||||
.plugin(yaak_fonts::init())
|
.plugin(yaak_fonts::init())
|
||||||
.plugin(yaak_git::init())
|
.plugin(yaak_git::init())
|
||||||
|
.plugin(yaak_http::init())
|
||||||
.plugin(yaak_ws::init())
|
.plugin(yaak_ws::init())
|
||||||
.plugin(yaak_sync::init());
|
.plugin(yaak_sync::init());
|
||||||
|
|
||||||
@@ -1299,6 +1360,11 @@ pub fn run() {
|
|||||||
builder = builder.plugin(yaak_license::init());
|
builder = builder.plugin(yaak_license::init());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "updater")]
|
||||||
|
{
|
||||||
|
builder = builder.plugin(tauri_plugin_updater::Builder::default().build());
|
||||||
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
{
|
{
|
||||||
@@ -1381,7 +1447,9 @@ pub fn run() {
|
|||||||
cmd_save_response,
|
cmd_save_response,
|
||||||
cmd_send_ephemeral_request,
|
cmd_send_ephemeral_request,
|
||||||
cmd_send_http_request,
|
cmd_send_http_request,
|
||||||
cmd_template_functions,
|
cmd_send_folder,
|
||||||
|
cmd_template_function_config,
|
||||||
|
cmd_template_function_summaries,
|
||||||
cmd_template_tokens_to_string,
|
cmd_template_tokens_to_string,
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -1399,7 +1467,7 @@ pub fn run() {
|
|||||||
let _ = window::create_main_window(app_handle, "/");
|
let _ = window::create_main_window(app_handle, "/");
|
||||||
let h = app_handle.clone();
|
let h = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let info = history::store_launch_history(&h).await;
|
let info = history::get_or_upsert_launch_info(&h);
|
||||||
debug!("Launched Yaak {:?}", info);
|
debug!("Launched Yaak {:?}", info);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1499,7 +1567,30 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
|
|||||||
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
||||||
// want to block here
|
// want to block here
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
|
let ev = plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
|
||||||
|
|
||||||
|
let ev = match ev {
|
||||||
|
Ok(Some(ev)) => ev,
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to handle plugin event: {e:?}");
|
||||||
|
let _ = app_handle.emit(
|
||||||
|
"show_toast",
|
||||||
|
InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||||
|
message: e.to_string(),
|
||||||
|
color: Some(Color::Danger),
|
||||||
|
icon: None,
|
||||||
|
timeout: Some(30000),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||||
|
if let Err(e) = plugin_manager.reply(&event, &ev).await {
|
||||||
|
warn!("Failed to reply to plugin manager: {:?}", e)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
plugin_manager.unsubscribe(rx_id.as_str()).await;
|
plugin_manager.unsubscribe(rx_id.as_str()).await;
|
||||||
@@ -1531,14 +1622,19 @@ async fn call_frontend<R: Runtime>(
|
|||||||
v.to_owned()
|
v.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_window_from_window_context<R: Runtime>(
|
fn get_window_from_plugin_context<R: Runtime>(
|
||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
window_context: &PluginWindowContext,
|
plugin_context: &PluginContext,
|
||||||
) -> Option<WebviewWindow<R>> {
|
) -> Result<WebviewWindow<R>> {
|
||||||
let label = match window_context {
|
let label = match &plugin_context.label {
|
||||||
PluginWindowContext::Label { label, .. } => label,
|
Some(label) => label,
|
||||||
PluginWindowContext::None => {
|
None => {
|
||||||
return app_handle.webview_windows().iter().next().map(|(_, w)| w.to_owned());
|
return app_handle
|
||||||
|
.webview_windows()
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|(_, w)| w.to_owned())
|
||||||
|
.ok_or(GenericError("No windows open".to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1548,10 +1644,10 @@ fn get_window_from_window_context<R: Runtime>(
|
|||||||
.find_map(|(_, w)| if w.label() == label { Some(w.to_owned()) } else { None });
|
.find_map(|(_, w)| if w.label() == label { Some(w.to_owned()) } else { None });
|
||||||
|
|
||||||
if window.is_none() {
|
if window.is_none() {
|
||||||
error!("Failed to find window by {window_context:?}");
|
error!("Failed to find window by {plugin_context:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
window
|
Ok(window.ok_or(GenericError(format!("Failed to find window for {}", label)))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {
|
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::history::get_num_launches;
|
use crate::history::get_or_upsert_launch_info;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::debug;
|
use log::{debug, info};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||||
@@ -77,6 +77,13 @@ impl YaakNotifier {
|
|||||||
|
|
||||||
self.last_check = SystemTime::now();
|
self.last_check = SystemTime::now();
|
||||||
|
|
||||||
|
if !app_handle.db().get_settings().check_notifications {
|
||||||
|
info!("Notifications are disabled. Skipping check.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Checking for notifications");
|
||||||
|
|
||||||
#[cfg(feature = "license")]
|
#[cfg(feature = "license")]
|
||||||
let license_check = {
|
let license_check = {
|
||||||
use yaak_license::{LicenseCheckStatus, check_license};
|
use yaak_license::{LicenseCheckStatus, check_license};
|
||||||
@@ -91,17 +98,17 @@ impl YaakNotifier {
|
|||||||
#[cfg(not(feature = "license"))]
|
#[cfg(not(feature = "license"))]
|
||||||
let license_check = "disabled".to_string();
|
let license_check = "disabled".to_string();
|
||||||
|
|
||||||
let settings = window.db().get_settings();
|
let launch_info = get_or_upsert_launch_info(app_handle);
|
||||||
let num_launches = get_num_launches(app_handle).await;
|
|
||||||
let info = app_handle.package_info().clone();
|
|
||||||
let req = yaak_api_client(app_handle)?
|
let req = yaak_api_client(app_handle)?
|
||||||
.request(Method::GET, "https://notify.yaak.app/notifications")
|
.request(Method::GET, "https://notify.yaak.app/notifications")
|
||||||
.query(&[
|
.query(&[
|
||||||
("version", info.version.to_string().as_str()),
|
("version", &launch_info.current_version),
|
||||||
("launches", num_launches.to_string().as_str()),
|
("version_prev", &launch_info.previous_version),
|
||||||
("installed", settings.created_at.format("%Y-%m-%d").to_string().as_str()),
|
("launches", &launch_info.num_launches.to_string()),
|
||||||
|
("installed", &launch_info.user_since.format("%Y-%m-%d").to_string()),
|
||||||
("license", &license_check),
|
("license", &license_check),
|
||||||
("platform", get_os()),
|
("updates", &get_updater_status(app_handle).to_string()),
|
||||||
|
("platform", &get_os().to_string()),
|
||||||
]);
|
]);
|
||||||
let resp = req.send().await?;
|
let resp = req.send().await?;
|
||||||
if resp.status() != 200 {
|
if resp.status() != 200 {
|
||||||
@@ -131,3 +138,33 @@ async fn get_kv<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Vec<String>> {
|
|||||||
Some(v) => Ok(serde_json::from_str(&v.value)?),
|
Some(v) => Ok(serde_json::from_str(&v.value)?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn get_updater_status<R: Runtime>(app_handle: &AppHandle<R>) -> &'static str {
|
||||||
|
#[cfg(not(feature = "updater"))]
|
||||||
|
{
|
||||||
|
// Updater is not enabled as a Rust feature
|
||||||
|
return "missing";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "updater", target_os = "linux"))]
|
||||||
|
{
|
||||||
|
let settings = app_handle.db().get_settings();
|
||||||
|
if !settings.autoupdate {
|
||||||
|
// Updates are explicitly disabled
|
||||||
|
"disabled"
|
||||||
|
} else if std::env::var("APPIMAGE").is_err() {
|
||||||
|
// Updates are enabled, but unsupported
|
||||||
|
"unsupported"
|
||||||
|
} else {
|
||||||
|
// Updates are enabled and supported
|
||||||
|
"enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "updater", not(target_os = "linux")))]
|
||||||
|
{
|
||||||
|
let settings = app_handle.db().get_settings();
|
||||||
|
if settings.autoupdate { "enabled" } else { "disabled" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
use crate::http_request::send_http_request;
|
use crate::error::Result;
|
||||||
|
use crate::http_request::send_http_request_with_context;
|
||||||
use crate::render::{render_grpc_request, render_http_request, render_json_value};
|
use crate::render::{render_grpc_request, render_http_request, render_json_value};
|
||||||
use crate::window::{CreateWindowConfig, create_window};
|
use crate::window::{CreateWindowConfig, create_window};
|
||||||
use crate::{
|
use crate::{
|
||||||
call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_window_context,
|
call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_plugin_context,
|
||||||
workspace_from_window,
|
workspace_from_window,
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use log::{error, warn};
|
use log::error;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, State};
|
use tauri::{AppHandle, Emitter, Manager, Runtime};
|
||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
|
use yaak_common::window::WorkspaceWindowTrait;
|
||||||
use yaak_models::models::{HttpResponse, Plugin};
|
use yaak_models::models::{HttpResponse, Plugin};
|
||||||
|
use yaak_models::queries::any_request::AnyRequest;
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
|
use yaak_plugins::error::Error::PluginErr;
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
|
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
|
||||||
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
|
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
|
||||||
InternalEventPayload, ListCookieNamesResponse, PluginWindowContext, RenderGrpcRequestResponse,
|
InternalEventPayload, ListCookieNamesResponse, RenderGrpcRequestResponse,
|
||||||
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
||||||
TemplateRenderResponse, WindowNavigateEvent,
|
TemplateRenderResponse, WindowInfoResponse, WindowNavigateEvent,
|
||||||
};
|
};
|
||||||
use yaak_plugins::manager::PluginManager;
|
|
||||||
use yaak_plugins::plugin_handle::PluginHandle;
|
use yaak_plugins::plugin_handle::PluginHandle;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
use yaak_templates::{RenderErrorBehavior, RenderOptions};
|
use yaak_templates::{RenderErrorBehavior, RenderOptions};
|
||||||
@@ -29,114 +32,114 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
event: &InternalEvent,
|
event: &InternalEvent,
|
||||||
plugin_handle: &PluginHandle,
|
plugin_handle: &PluginHandle,
|
||||||
) {
|
) -> Result<Option<InternalEventPayload>> {
|
||||||
// debug!("Got event to app {event:?}");
|
// log::debug!("Got event to app {event:?}");
|
||||||
let window_context = event.window_context.to_owned();
|
let plugin_context = event.context.to_owned();
|
||||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
match event.clone().payload {
|
||||||
InternalEventPayload::CopyTextRequest(req) => {
|
InternalEventPayload::CopyTextRequest(req) => {
|
||||||
app_handle
|
app_handle.clipboard().write_text(req.text.as_str())?;
|
||||||
.clipboard()
|
Ok(Some(InternalEventPayload::CopyTextResponse(EmptyPayload {})))
|
||||||
.write_text(req.text.as_str())
|
|
||||||
.expect("Failed to write text to clipboard");
|
|
||||||
Some(InternalEventPayload::CopyTextResponse(EmptyPayload {}))
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ShowToastRequest(req) => {
|
InternalEventPayload::ShowToastRequest(req) => {
|
||||||
match window_context {
|
match plugin_context.label {
|
||||||
PluginWindowContext::Label { label, .. } => app_handle
|
Some(label) => app_handle.emit_to(label, "show_toast", req)?,
|
||||||
.emit_to(label, "show_toast", req)
|
None => app_handle.emit("show_toast", req)?,
|
||||||
.expect("Failed to emit show_toast to window"),
|
|
||||||
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"),
|
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::ShowToastResponse(EmptyPayload {}))
|
Ok(Some(InternalEventPayload::ShowToastResponse(EmptyPayload {})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::PromptTextRequest(_) => {
|
InternalEventPayload::PromptTextRequest(_) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for render");
|
Ok(call_frontend(&window, event).await)
|
||||||
call_frontend(&window, event).await
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||||
let http_responses = app_handle
|
let http_responses = app_handle
|
||||||
.db()
|
.db()
|
||||||
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
Ok(Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||||
http_responses,
|
http_responses,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
||||||
let http_request = app_handle.db().get_http_request(&req.id).ok();
|
let http_request = app_handle.db().get_http_request(&req.id).ok();
|
||||||
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
||||||
http_request,
|
http_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::RenderGrpcRequestRequest(req) => {
|
InternalEventPayload::RenderGrpcRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for render grpc request");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let environment_chain = window.db().resolve_environments(
|
||||||
.db()
|
&workspace.id,
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
req.grpc_request.folder_id.as_deref(),
|
||||||
.expect("Failed to resolve environments");
|
environment_id.as_deref(),
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
)?;
|
||||||
|
let cb = PluginTemplateCallback::new(app_handle, &plugin_context, req.purpose);
|
||||||
let opt = RenderOptions {
|
let opt = RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt)
|
let grpc_request =
|
||||||
.await
|
render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt).await?;
|
||||||
.expect("Failed to render grpc request");
|
Ok(Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
|
||||||
Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
|
|
||||||
grpc_request,
|
grpc_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for render http request");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let environment_chain = window.db().resolve_environments(
|
||||||
.db()
|
&workspace.id,
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
req.http_request.folder_id.as_deref(),
|
||||||
.expect("Failed to resolve environments");
|
environment_id.as_deref(),
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
)?;
|
||||||
|
let cb = PluginTemplateCallback::new(app_handle, &plugin_context, req.purpose);
|
||||||
let opt = &RenderOptions {
|
let opt = &RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let http_request = render_http_request(&req.http_request, environment_chain, &cb, &opt)
|
let http_request =
|
||||||
.await
|
render_http_request(&req.http_request, environment_chain, &cb, &opt).await?;
|
||||||
.expect("Failed to render http request");
|
Ok(Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
||||||
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
|
||||||
http_request,
|
http_request,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::TemplateRenderRequest(req) => {
|
InternalEventPayload::TemplateRenderRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for render");
|
|
||||||
|
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
let environment_id = environment_from_window(&window).map(|e| e.id);
|
let environment_id = environment_from_window(&window).map(|e| e.id);
|
||||||
let environment_chain = window
|
let folder_id = if let Some(id) = window.request_id() {
|
||||||
.db()
|
match window.db().get_any_request(&id) {
|
||||||
.resolve_environments(&workspace.id, None, environment_id.as_deref())
|
Ok(AnyRequest::HttpRequest(r)) => r.folder_id,
|
||||||
.expect("Failed to resolve environments");
|
Ok(AnyRequest::GrpcRequest(r)) => r.folder_id,
|
||||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
Ok(AnyRequest::WebsocketRequest(r)) => r.folder_id,
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let environment_chain = window.db().resolve_environments(
|
||||||
|
&workspace.id,
|
||||||
|
folder_id.as_deref(),
|
||||||
|
environment_id.as_deref(),
|
||||||
|
)?;
|
||||||
|
let cb = PluginTemplateCallback::new(app_handle, &plugin_context, req.purpose);
|
||||||
let opt = RenderOptions {
|
let opt = RenderOptions {
|
||||||
error_behavior: RenderErrorBehavior::Throw,
|
error_behavior: RenderErrorBehavior::Throw,
|
||||||
};
|
};
|
||||||
let data = render_json_value(req.data, environment_chain, &cb, &opt)
|
let data = render_json_value(req.data, environment_chain, &cb, &opt).await?;
|
||||||
.await
|
Ok(Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })))
|
||||||
.expect("Failed to render template");
|
|
||||||
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ErrorResponse(resp) => {
|
InternalEventPayload::ErrorResponse(resp) => {
|
||||||
error!("Plugin error: {}: {:?}", resp.error, resp);
|
error!("Plugin error: {}: {:?}", resp.error, resp);
|
||||||
let toast_event = plugin_handle.build_event_to_send(
|
let toast_event = plugin_handle.build_event_to_send(
|
||||||
&window_context,
|
&plugin_context,
|
||||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Plugin error from {}: {}",
|
"Plugin error from {}: {}",
|
||||||
@@ -144,16 +147,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
resp.error
|
resp.error
|
||||||
),
|
),
|
||||||
color: Some(Color::Danger),
|
color: Some(Color::Danger),
|
||||||
timeout: None,
|
timeout: Some(30000),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
|
||||||
None
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::ReloadResponse(req) => {
|
InternalEventPayload::ReloadResponse(req) => {
|
||||||
let plugins = app_handle.db().list_plugins().unwrap();
|
let plugins = app_handle.db().list_plugins()?;
|
||||||
for plugin in plugins {
|
for plugin in plugins {
|
||||||
if plugin.directory != plugin_handle.dir {
|
if plugin.directory != plugin_handle.dir {
|
||||||
continue;
|
continue;
|
||||||
@@ -163,13 +165,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
||||||
..plugin
|
..plugin
|
||||||
};
|
};
|
||||||
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
|
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !req.silent {
|
if !req.silent {
|
||||||
let info = plugin_handle.info();
|
let info = plugin_handle.info();
|
||||||
let toast_event = plugin_handle.build_event_to_send(
|
let toast_event = plugin_handle.build_event_to_send(
|
||||||
&window_context,
|
&plugin_context,
|
||||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||||
message: format!("Reloaded plugin {}@{}", info.name, info.version),
|
message: format!("Reloaded plugin {}@{}", info.name, info.version),
|
||||||
icon: Some(Icon::Info),
|
icon: Some(Icon::Info),
|
||||||
@@ -178,13 +180,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
InternalEventPayload::SendHttpRequestRequest(req) => {
|
InternalEventPayload::SendHttpRequestRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for sending HTTP request");
|
|
||||||
let mut http_request = req.http_request;
|
let mut http_request = req.http_request;
|
||||||
let workspace =
|
let workspace =
|
||||||
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
|
||||||
@@ -198,37 +200,30 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
let http_response = if http_request.id.is_empty() {
|
let http_response = if http_request.id.is_empty() {
|
||||||
HttpResponse::default()
|
HttpResponse::default()
|
||||||
} else {
|
} else {
|
||||||
window
|
window.db().upsert_http_response(
|
||||||
.db()
|
&HttpResponse {
|
||||||
.upsert_http_response(
|
request_id: http_request.id.clone(),
|
||||||
&HttpResponse {
|
workspace_id: http_request.workspace_id.clone(),
|
||||||
request_id: http_request.id.clone(),
|
..Default::default()
|
||||||
workspace_id: http_request.workspace_id.clone(),
|
},
|
||||||
..Default::default()
|
&UpdateSource::Plugin,
|
||||||
},
|
)?
|
||||||
&UpdateSource::Plugin,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = send_http_request(
|
let http_response = send_http_request_with_context(
|
||||||
&window,
|
&window,
|
||||||
&http_request,
|
&http_request,
|
||||||
&http_response,
|
&http_response,
|
||||||
environment,
|
environment,
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
||||||
|
&plugin_context,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
let http_response = match result {
|
Ok(Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
||||||
Ok(r) => r,
|
|
||||||
Err(_e) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
|
||||||
http_response,
|
http_response,
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::OpenWindowRequest(req) => {
|
InternalEventPayload::OpenWindowRequest(req) => {
|
||||||
let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128);
|
let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128);
|
||||||
@@ -245,25 +240,25 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
};
|
};
|
||||||
if let Err(e) = create_window(app_handle, win_config) {
|
if let Err(e) = create_window(app_handle, win_config) {
|
||||||
let error_event = plugin_handle.build_event_to_send(
|
let error_event = plugin_handle.build_event_to_send(
|
||||||
&window_context,
|
&plugin_context,
|
||||||
&InternalEventPayload::ErrorResponse(ErrorResponse {
|
&InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||||
error: format!("Failed to create window: {:?}", e),
|
error: format!("Failed to create window: {:?}", e),
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle)).await;
|
return Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle))
|
||||||
return;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let event_id = event.id.clone();
|
let event_id = event.id.clone();
|
||||||
let plugin_handle = plugin_handle.clone();
|
let plugin_handle = plugin_handle.clone();
|
||||||
let window_context = window_context.clone();
|
let plugin_context = plugin_context.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
while let Some(url) = navigation_rx.recv().await {
|
while let Some(url) = navigation_rx.recv().await {
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
let event_to_send = plugin_handle.build_event_to_send(
|
let event_to_send = plugin_handle.build_event_to_send(
|
||||||
&window_context, // NOTE: Sending existing context on purpose here
|
&plugin_context, // NOTE: Sending existing context on purpose here
|
||||||
&InternalEventPayload::WindowNavigateEvent(WindowNavigateEvent { url }),
|
&InternalEventPayload::WindowNavigateEvent(WindowNavigateEvent { url }),
|
||||||
Some(event_id.clone()),
|
Some(event_id.clone()),
|
||||||
);
|
);
|
||||||
@@ -275,11 +270,11 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
{
|
{
|
||||||
let event_id = event.id.clone();
|
let event_id = event.id.clone();
|
||||||
let plugin_handle = plugin_handle.clone();
|
let plugin_handle = plugin_handle.clone();
|
||||||
let window_context = window_context.clone();
|
let plugin_context = plugin_context.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
while let Some(_) = close_rx.recv().await {
|
while let Some(_) = close_rx.recv().await {
|
||||||
let event_to_send = plugin_handle.build_event_to_send(
|
let event_to_send = plugin_handle.build_event_to_send(
|
||||||
&window_context,
|
&plugin_context,
|
||||||
&InternalEventPayload::WindowCloseEvent,
|
&InternalEventPayload::WindowCloseEvent,
|
||||||
Some(event_id.clone()),
|
Some(event_id.clone()),
|
||||||
);
|
);
|
||||||
@@ -288,32 +283,33 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
InternalEventPayload::CloseWindowRequest(req) => {
|
InternalEventPayload::CloseWindowRequest(req) => {
|
||||||
if let Some(window) = app_handle.webview_windows().get(&req.label) {
|
if let Some(window) = app_handle.webview_windows().get(&req.label) {
|
||||||
window.close().expect("Failed to close window");
|
window.close()?;
|
||||||
}
|
}
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
InternalEventPayload::SetKeyValueRequest(req) => {
|
InternalEventPayload::SetKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
|
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
|
||||||
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {}))
|
Ok(Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetKeyValueRequest(req) => {
|
InternalEventPayload::GetKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
|
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
|
||||||
Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value }))
|
Ok(Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value })))
|
||||||
}
|
}
|
||||||
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
||||||
let name = plugin_handle.info().name;
|
let name = plugin_handle.info().name;
|
||||||
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap();
|
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key)?;
|
||||||
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
|
Ok(Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse {
|
||||||
|
deleted,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::ListCookieNamesRequest(_req) => {
|
InternalEventPayload::ListCookieNamesRequest(_req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for listing cookies");
|
|
||||||
let names = match cookie_jar_from_window(&window) {
|
let names = match cookie_jar_from_window(&window) {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
Some(j) => j
|
Some(j) => j
|
||||||
@@ -322,11 +318,12 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
|
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse { names }))
|
Ok(Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse {
|
||||||
|
names,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
InternalEventPayload::GetCookieValueRequest(req) => {
|
InternalEventPayload::GetCookieValueRequest(req) => {
|
||||||
let window = get_window_from_window_context(app_handle, &window_context)
|
let window = get_window_from_plugin_context(app_handle, &plugin_context)?;
|
||||||
.expect("Failed to find window for listing cookies");
|
|
||||||
let value = match cookie_jar_from_window(&window) {
|
let value = match cookie_jar_from_window(&window) {
|
||||||
None => None,
|
None => None,
|
||||||
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
|
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
|
||||||
@@ -336,15 +333,31 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
_ => None,
|
_ => None,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
|
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
|
||||||
}
|
}
|
||||||
_ => None,
|
InternalEventPayload::WindowInfoRequest(req) => {
|
||||||
};
|
let w = app_handle
|
||||||
|
.get_webview_window(&req.label)
|
||||||
|
.ok_or(PluginErr(format!("Failed to find window for {}", req.label)))?;
|
||||||
|
|
||||||
if let Some(e) = response_event {
|
// Actually look up the data so we never return an invalid ID
|
||||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
let environment_id = environment_from_window(&w).map(|m| m.id);
|
||||||
if let Err(e) = plugin_manager.reply(&event, &e).await {
|
let workspace_id = workspace_from_window(&w).map(|m| m.id);
|
||||||
warn!("Failed to reply to plugin manager: {:?}", e)
|
let request_id =
|
||||||
|
match app_handle.db().get_any_request(&w.request_id().unwrap_or_default()) {
|
||||||
|
Ok(AnyRequest::HttpRequest(r)) => Some(r.id),
|
||||||
|
Ok(AnyRequest::WebsocketRequest(r)) => Some(r.id),
|
||||||
|
Ok(AnyRequest::GrpcRequest(r)) => Some(r.id),
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(InternalEventPayload::WindowInfoResponse(WindowInfoResponse {
|
||||||
|
label: w.label().to_string(),
|
||||||
|
request_id,
|
||||||
|
workspace_id,
|
||||||
|
environment_id,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use yaak_http::apply_path_placeholders;
|
use yaak_http::path_placeholders::apply_path_placeholders;
|
||||||
use yaak_models::models::{
|
use yaak_models::models::{
|
||||||
Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
|
Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
|
||||||
};
|
};
|
||||||
use yaak_models::render::make_vars_hashmap;
|
use yaak_models::render::make_vars_hashmap;
|
||||||
use yaak_templates::{RenderOptions, TemplateCallback, parse_and_render, render_json_value_raw};
|
use yaak_templates::{parse_and_render, render_json_value_raw, RenderOptions, TemplateCallback};
|
||||||
|
|
||||||
pub async fn render_template<T: TemplateCallback>(
|
pub async fn render_template<T: TemplateCallback>(
|
||||||
template: &str,
|
template: &str,
|
||||||
@@ -70,6 +70,9 @@ pub async fn render_http_request<T: TemplateCallback>(
|
|||||||
|
|
||||||
let mut url_parameters = Vec::new();
|
let mut url_parameters = Vec::new();
|
||||||
for p in r.url_parameters.clone() {
|
for p in r.url_parameters.clone() {
|
||||||
|
if !p.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
url_parameters.push(HttpUrlParameter {
|
url_parameters.push(HttpUrlParameter {
|
||||||
enabled: p.enabled,
|
enabled: p.enabled,
|
||||||
name: parse_and_render(p.name.as_str(), vars, cb, &opt).await?,
|
name: parse_and_render(p.name.as_str(), vars, cb, &opt).await?,
|
||||||
@@ -80,6 +83,9 @@ pub async fn render_http_request<T: TemplateCallback>(
|
|||||||
|
|
||||||
let mut headers = Vec::new();
|
let mut headers = Vec::new();
|
||||||
for p in r.headers.clone() {
|
for p in r.headers.clone() {
|
||||||
|
if !p.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
headers.push(HttpRequestHeader {
|
headers.push(HttpRequestHeader {
|
||||||
enabled: p.enabled,
|
enabled: p.enabled,
|
||||||
name: parse_and_render(p.name.as_str(), vars, cb, &opt).await?,
|
name: parse_and_render(p.name.as_str(), vars, cb, &opt).await?,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
use crate::error::Result;
|
||||||
use crate::window_menu::app_menu;
|
use crate::window_menu::app_menu;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use tauri::{
|
use tauri::{
|
||||||
AppHandle, Emitter, LogicalSize, Manager, Runtime, WebviewUrl, WebviewWindow, WindowEvent,
|
AppHandle, Emitter, LogicalSize, Manager, PhysicalSize, Runtime, WebviewUrl, WebviewWindow, WindowEvent
|
||||||
};
|
};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use crate::error::Result;
|
|
||||||
|
|
||||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||||
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
||||||
@@ -49,7 +49,6 @@ pub(crate) fn create_window<R: Runtime>(
|
|||||||
.resizable(true)
|
.resizable(true)
|
||||||
.visible(false) // To prevent theme flashing, the frontend code calls show() immediately after configuring the theme
|
.visible(false) // To prevent theme flashing, the frontend code calls show() immediately after configuring the theme
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
|
||||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
||||||
|
|
||||||
if let Some(key) = config.data_dir_key {
|
if let Some(key) = config.data_dir_key {
|
||||||
@@ -161,6 +160,11 @@ pub(crate) fn create_window<R: Runtime>(
|
|||||||
"dev.reset_size" => webview_window
|
"dev.reset_size" => webview_window
|
||||||
.set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT))
|
.set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
"dev.reset_size_record" => {
|
||||||
|
let width = webview_window.outer_size().unwrap().width;
|
||||||
|
let height = width * 9 / 16;
|
||||||
|
webview_window.set_size(PhysicalSize::new(width, height)).unwrap()
|
||||||
|
}
|
||||||
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
|
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
|
||||||
"dev.generate_theme_css" => {
|
"dev.generate_theme_css" => {
|
||||||
w.emit("generate_theme_css", true).unwrap();
|
w.emit("generate_theme_css", true).unwrap();
|
||||||
@@ -216,10 +220,10 @@ pub(crate) fn create_child_window(
|
|||||||
) -> Result<WebviewWindow> {
|
) -> Result<WebviewWindow> {
|
||||||
let app_handle = parent_window.app_handle();
|
let app_handle = parent_window.app_handle();
|
||||||
let label = format!("{OTHER_WINDOW_PREFIX}_{label}");
|
let label = format!("{OTHER_WINDOW_PREFIX}_{label}");
|
||||||
let scale_factor = parent_window.scale_factor().unwrap();
|
let scale_factor = parent_window.scale_factor()?;
|
||||||
|
|
||||||
let current_pos = parent_window.inner_position().unwrap().to_logical::<f64>(scale_factor);
|
let current_pos = parent_window.inner_position()?.to_logical::<f64>(scale_factor);
|
||||||
let current_size = parent_window.inner_size().unwrap().to_logical::<f64>(scale_factor);
|
let current_size = parent_window.inner_size()?.to_logical::<f64>(scale_factor);
|
||||||
|
|
||||||
// Position the new window in the middle of the parent
|
// Position the new window in the middle of the parent
|
||||||
let position = (
|
let position = (
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>>
|
|||||||
.build(app_handle)?,
|
.build(app_handle)?,
|
||||||
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
|
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
|
||||||
.build(app_handle)?,
|
.build(app_handle)?,
|
||||||
|
&MenuItemBuilder::with_id("dev.reset_size_record".to_string(), "Reset Size 16x9")
|
||||||
|
.build(app_handle)?,
|
||||||
&MenuItemBuilder::with_id(
|
&MenuItemBuilder::with_id(
|
||||||
"dev.generate_theme_css".to_string(),
|
"dev.generate_theme_css".to_string(),
|
||||||
"Generate Theme CSS",
|
"Generate Theme CSS",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"createUpdaterArtifacts": "v1Compatible",
|
"createUpdaterArtifacts": true,
|
||||||
"windows": {
|
"windows": {
|
||||||
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a Yaak -c yaakapp %1"
|
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a Yaak -c yaakapp %1"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod window;
|
pub mod window;
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
pub mod api_client;
|
pub mod api_client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub trait WorkspaceWindowTrait {
|
|||||||
fn workspace_id(&self) -> Option<String>;
|
fn workspace_id(&self) -> Option<String>;
|
||||||
fn cookie_jar_id(&self) -> Option<String>;
|
fn cookie_jar_id(&self) -> Option<String>;
|
||||||
fn environment_id(&self) -> Option<String>;
|
fn environment_id(&self) -> Option<String>;
|
||||||
|
fn request_id(&self) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
||||||
@@ -28,4 +29,10 @@ impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
|
|||||||
let mut query_pairs = url.query_pairs();
|
let mut query_pairs = url.query_pairs();
|
||||||
query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string())
|
query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn request_id(&self) -> Option<String> {
|
||||||
|
let url = self.url().unwrap();
|
||||||
|
let mut query_pairs = url.query_pairs();
|
||||||
|
query_pairs.find(|(k, _v)| k == "request_id").map(|(_k, v)| v.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user