mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:14:03 +01:00
Support binary responses!
This commit is contained in:
81
package-lock.json
generated
81
package-lock.json
generated
@@ -27,6 +27,7 @@
|
||||
"@tanstack/react-query-persist-client": "^4.28.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.4-canary-b30a2325.0",
|
||||
"codemirror": "^6.0.1",
|
||||
@@ -2820,6 +2821,25 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -2876,6 +2896,29 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
@@ -4809,6 +4852,25 @@
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
@@ -9325,6 +9387,11 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -9359,6 +9426,15 @@
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
@@ -10812,6 +10888,11 @@
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@tanstack/react-query-persist-client": "^4.28.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.4-canary-b30a2325.0",
|
||||
"codemirror": "^6.0.1",
|
||||
|
||||
@@ -20,7 +20,7 @@ cocoa = "0.24.1"
|
||||
[dependencies]
|
||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["config-toml", "devtools", "shell-open", "system-tray", "updater", "window-start-dragging"] }
|
||||
tauri = { version = "1.2", features = ["config-toml", "devtools", "fs-read-file", "protocol-asset", "shell-open", "system-tray", "updater", "window-start-dragging"] }
|
||||
http = "0.2.8"
|
||||
reqwest = { version = "0.11.14", features = ["json"] }
|
||||
tokio = { version = "1.25.0", features = ["sync"] }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
DELETE FROM main.http_responses;
|
||||
ALTER TABLE http_responses DROP COLUMN body;
|
||||
ALTER TABLE http_responses ADD COLUMN body BLOB;
|
||||
ALTER TABLE http_responses ADD COLUMN body_path TEXT;
|
||||
ALTER TABLE http_responses ADD COLUMN content_length INTEGER;
|
||||
@@ -68,16 +68,6 @@
|
||||
},
|
||||
"query": "\n DELETE FROM http_responses\n WHERE request_id = ?\n "
|
||||
},
|
||||
"318ed5a1126fe00719393cf4e6c788ee5a265af88b7253f61a475f78c6774ef6": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 9
|
||||
}
|
||||
},
|
||||
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n body,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
|
||||
},
|
||||
"448a1d1f1866ab42c0f81fcf8eb2930bf21dfdd43ca4831bc1a198cf45ac3732": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -88,6 +78,16 @@
|
||||
},
|
||||
"query": "\n DELETE FROM http_requests\n WHERE id = ?\n "
|
||||
},
|
||||
"62475fd9483fb5eda01c937949da2ef66ac7005b4be06b87aa6210d462348aca": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 10
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE http_responses SET (\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n error,\n headers,\n updated_at\n ) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||
},
|
||||
"6f0cb5a6d1e8dbc8cdfcc3c7e7944b2c83c22cb795b9d6b98fe067dabec9680b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -194,15 +194,15 @@
|
||||
},
|
||||
"query": "\n DELETE FROM workspaces\n WHERE id = ?\n "
|
||||
},
|
||||
"a83698dcf9a815b881097133edb31a34ba25e7c6c114d463c495342a85371639": {
|
||||
"8947a2a90478277c42fe9b06bc1fa98197642a4d281a3dbc101be2c9c1fec36c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
"Right": 11
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =\n (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||
"query": "\n INSERT INTO http_responses (\n id,\n request_id,\n workspace_id,\n elapsed,\n url,\n status,\n status_reason,\n content_length,\n body,\n body_path,\n headers\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\n "
|
||||
},
|
||||
"b19c275180909a39342b13c3cdcf993781636913ae590967f5508c46a56dc961": {
|
||||
"describe": {
|
||||
@@ -214,6 +214,108 @@
|
||||
},
|
||||
"query": "\n INSERT INTO http_requests (\n id,\n workspace_id,\n name,\n url,\n method,\n body,\n body_type,\n authentication,\n authentication_type,\n headers,\n sort_priority\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n name = excluded.name,\n method = excluded.method,\n headers = excluded.headers,\n body = excluded.body,\n body_type = excluded.body_type,\n authentication = excluded.authentication,\n authentication_type = excluded.authentication_type,\n url = excluded.url,\n sort_priority = excluded.sort_priority\n "
|
||||
},
|
||||
"c23c61b05a4c9e04ab0c1fc2c579d6f2a82a37aeed8addf9861b4985f2a5422e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "request_id",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "status_reason",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "content_length",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"ordinal": 10,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "body_path",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "elapsed",
|
||||
"ordinal": 12,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
||||
},
|
||||
"caf3f21bf291dfbd36446592066e96c1f83abe96f6ea9211a3e049eb9c58a8c8": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -406,96 +508,6 @@
|
||||
},
|
||||
"query": "\n SELECT\n id,\n model,\n workspace_id,\n created_at,\n updated_at,\n name,\n url,\n method,\n body,\n body_type,\n authentication AS \"authentication!: Json<HashMap<String, JsonValue>>\",\n authentication_type,\n sort_priority,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpRequestHeader>>\"\n FROM http_requests\n WHERE id = ?\n "
|
||||
},
|
||||
"d5ad6d5f82fe837fa9215bd4619ec18a7c95b3088d4fbf9825f2d1d28069d1ce": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "request_id",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "status_reason",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "elapsed",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at,\n created_at, status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
|
||||
},
|
||||
"d80c09497771e3641022e73ec6c6a87e73a551f88a948a5445d754922b82b50b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -516,7 +528,7 @@
|
||||
},
|
||||
"query": "\n UPDATE workspaces SET (name, updated_at) =\n (?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||
},
|
||||
"e3ade0a69348d512e47e964bded9d7d890b92fdc1e01c6c22fa5e91f943639f2": {
|
||||
"e7bba61f8d60fb021c027d829b070e7087041e0f30252754aaaa85223b614896": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -550,38 +562,48 @@
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"name": "url",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "status_reason",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "elapsed",
|
||||
"name": "content_length",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"name": "body",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"name": "body_path",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||
"name": "elapsed",
|
||||
"ordinal": 12,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
@@ -593,9 +615,11 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
@@ -604,7 +628,7 @@
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at,\n status, status_reason, body, elapsed, url, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE id = ?\n "
|
||||
"query": "\n SELECT id, model, workspace_id, request_id, updated_at, created_at, url,\n status, status_reason, content_length, body, body_path, elapsed, error,\n headers AS \"headers!: sqlx::types::Json<Vec<HttpResponseHeader>>\"\n FROM http_responses\n WHERE request_id = ?\n ORDER BY created_at ASC\n "
|
||||
},
|
||||
"f116d8cf9aad828135bb8c3a4c8b8e6b857ae13303989e9133a33b2d1cf20e96": {
|
||||
"describe": {
|
||||
|
||||
@@ -9,7 +9,8 @@ extern crate objc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env::current_dir;
|
||||
use std::fs::create_dir_all;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::Write;
|
||||
|
||||
use base64::Engine;
|
||||
use http::header::{HeaderName, ACCEPT, USER_AGENT};
|
||||
@@ -221,9 +222,29 @@ async fn actually_send_ephemeral_request(
|
||||
.collect(),
|
||||
);
|
||||
response.url = v.url().to_string();
|
||||
response.body = v.text().await.expect("Failed to get body");
|
||||
let body_bytes = v.bytes().await.expect("Failed to get body").to_vec();
|
||||
response.content_length = Some(body_bytes.len() as i64);
|
||||
if body_bytes.len() > 1000 {
|
||||
let dir = app_handle.path_resolver().app_data_dir().unwrap();
|
||||
let body_path = dir.join(response.id.clone());
|
||||
let mut f = File::options()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&body_path)
|
||||
.expect("Failed to open file");
|
||||
f.write_all(body_bytes.as_slice())
|
||||
.expect("Failed to write to file");
|
||||
response.body_path = Some(
|
||||
body_path
|
||||
.to_str()
|
||||
.expect("Failed to get body path")
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
response.body = Some(body_bytes);
|
||||
}
|
||||
response.elapsed = start.elapsed().as_millis() as i64;
|
||||
response = models::update_response_if_id(response, pool)
|
||||
response = models::update_response_if_id(&response, pool)
|
||||
.await
|
||||
.expect("Failed to update response");
|
||||
if request.id != "" {
|
||||
@@ -247,7 +268,7 @@ async fn send_request(
|
||||
.await
|
||||
.expect("Failed to get request");
|
||||
|
||||
let response = models::create_response(&req.id, 0, "", 0, None, "", vec![], pool)
|
||||
let response = models::create_response(&req.id, 0, "", 0, None, None, None, None, vec![], pool)
|
||||
.await
|
||||
.expect("Failed to create response");
|
||||
|
||||
@@ -271,7 +292,7 @@ async fn response_err(
|
||||
) -> Result<models::HttpResponse, String> {
|
||||
let mut response = response.clone();
|
||||
response.error = Some(error.clone());
|
||||
response = models::update_response_if_id(response, pool)
|
||||
response = models::update_response_if_id(&response, pool)
|
||||
.await
|
||||
.expect("Failed to update response");
|
||||
emit_side_effect(app_handle, "updated_model", &response);
|
||||
|
||||
@@ -63,10 +63,12 @@ pub struct HttpResponse {
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub error: Option<String>,
|
||||
pub url: String,
|
||||
pub content_length: Option<i64>,
|
||||
pub elapsed: i64,
|
||||
pub status: i64,
|
||||
pub status_reason: Option<String>,
|
||||
pub body: String,
|
||||
pub body: Option<Vec<u8>>,
|
||||
pub body_path: Option<String>,
|
||||
pub headers: Json<Vec<HttpResponseHeader>>,
|
||||
}
|
||||
|
||||
@@ -373,7 +375,9 @@ pub async fn create_response(
|
||||
url: &str,
|
||||
status: i64,
|
||||
status_reason: Option<&str>,
|
||||
body: &str,
|
||||
content_length: Option<i64>,
|
||||
body: Option<Vec<u8>>,
|
||||
body_path: Option<&str>,
|
||||
headers: Vec<HttpResponseHeader>,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
@@ -392,10 +396,12 @@ pub async fn create_response(
|
||||
url,
|
||||
status,
|
||||
status_reason,
|
||||
content_length,
|
||||
body,
|
||||
body_path,
|
||||
headers
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
"#,
|
||||
id,
|
||||
request_id,
|
||||
@@ -404,7 +410,9 @@ pub async fn create_response(
|
||||
url,
|
||||
status,
|
||||
status_reason,
|
||||
content_length,
|
||||
body,
|
||||
body_path,
|
||||
headers_json,
|
||||
)
|
||||
.execute(pool)
|
||||
@@ -415,11 +423,11 @@ pub async fn create_response(
|
||||
}
|
||||
|
||||
pub async fn update_response_if_id(
|
||||
response: HttpResponse,
|
||||
response: &HttpResponse,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
if response.id == "" {
|
||||
return Ok(response);
|
||||
return Ok(response.clone());
|
||||
}
|
||||
return update_response(response, pool).await;
|
||||
}
|
||||
@@ -444,20 +452,32 @@ pub async fn update_workspace(
|
||||
}
|
||||
|
||||
pub async fn update_response(
|
||||
response: HttpResponse,
|
||||
response: &HttpResponse,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<HttpResponse, sqlx::Error> {
|
||||
let headers_json = Json(response.headers);
|
||||
let headers_json = Json(&response.headers);
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE http_responses SET (elapsed, url, status, status_reason, body, error, headers, updated_at) =
|
||||
(?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||
UPDATE http_responses SET (
|
||||
elapsed,
|
||||
url,
|
||||
status,
|
||||
status_reason,
|
||||
content_length,
|
||||
body,
|
||||
body_path,
|
||||
error,
|
||||
headers,
|
||||
updated_at
|
||||
) = (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||
"#,
|
||||
response.elapsed,
|
||||
response.url,
|
||||
response.status,
|
||||
response.status_reason,
|
||||
response.content_length,
|
||||
response.body,
|
||||
response.body_path,
|
||||
response.error,
|
||||
headers_json,
|
||||
response.id,
|
||||
@@ -469,11 +489,11 @@ pub async fn update_response(
|
||||
}
|
||||
|
||||
pub async fn get_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse, sqlx::Error> {
|
||||
sqlx::query_as_unchecked!(
|
||||
sqlx::query_as!(
|
||||
HttpResponse,
|
||||
r#"
|
||||
SELECT id, model, workspace_id, request_id, updated_at, created_at,
|
||||
status, status_reason, body, elapsed, url, error,
|
||||
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
||||
status, status_reason, content_length, body, body_path, elapsed, error,
|
||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||
FROM http_responses
|
||||
WHERE id = ?
|
||||
@@ -491,8 +511,8 @@ pub async fn find_responses(
|
||||
sqlx::query_as!(
|
||||
HttpResponse,
|
||||
r#"
|
||||
SELECT id, model, workspace_id, request_id, updated_at,
|
||||
created_at, status, status_reason, body, elapsed, url, error,
|
||||
SELECT id, model, workspace_id, request_id, updated_at, created_at, url,
|
||||
status, status_reason, content_length, body, body_path, elapsed, error,
|
||||
headers AS "headers!: sqlx::types::Json<Vec<HttpResponseHeader>>"
|
||||
FROM http_responses
|
||||
WHERE request_id = ?
|
||||
|
||||
@@ -14,9 +14,15 @@
|
||||
"windows": [],
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"protocol": {
|
||||
"assetScope": ["$APPDATA/*"],
|
||||
"asset": true
|
||||
},
|
||||
"fs": {
|
||||
"readFile": true,
|
||||
"scope": [
|
||||
"$RESOURCE/*"
|
||||
"$RESOURCE/*",
|
||||
"$APPDATA/*"
|
||||
]
|
||||
},
|
||||
"shell": {
|
||||
@@ -60,7 +66,8 @@
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"security": {},
|
||||
"security": {
|
||||
},
|
||||
"systemTray": {
|
||||
"iconAsTemplate": true,
|
||||
"iconPath": "icons/icon.png"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
interface Props {
|
||||
data: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function ImageView({ data }: Props) {
|
||||
// const dataUri = `data:image/png;base64,${window.btoa(data)}`;
|
||||
return <div>Image preview not supported until binary response support is added</div>;
|
||||
}
|
||||
@@ -5,26 +5,26 @@ import { createGlobalState } from 'react-use';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useDeleteResponse } from '../hooks/useDeleteResponse';
|
||||
import { useDeleteResponses } from '../hooks/useDeleteResponses';
|
||||
import { useResponseContentType } from '../hooks/useResponseContentType';
|
||||
import { useResponses } from '../hooks/useResponses';
|
||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
import { pluralize } from '../lib/pluralize';
|
||||
import { Banner } from './core/Banner';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Editor } from './core/Editor';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { StatusTag } from './core/StatusTag';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { Webview } from './core/Webview';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { ImageView } from './ImageView';
|
||||
import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { TextViewer } from './responseViewers/TextViewer';
|
||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||
|
||||
interface Props {
|
||||
style?: CSSProperties;
|
||||
@@ -48,12 +48,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
// Unset pinned response when a new one comes in
|
||||
useEffect(() => setPinnedResponseId(null), [responses.length]);
|
||||
|
||||
const contentType = useMemo(
|
||||
() =>
|
||||
activeResponse?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ??
|
||||
'text/plain',
|
||||
[activeResponse],
|
||||
);
|
||||
const contentType = useResponseContentType(activeResponse);
|
||||
|
||||
const tabs: TabItem[] = useMemo(
|
||||
() => [
|
||||
@@ -84,9 +79,6 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
[activeResponse?.headers, setViewMode, viewMode],
|
||||
);
|
||||
|
||||
// Don't render until we know the view mode
|
||||
if (viewMode === undefined) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
@@ -119,10 +111,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
<span>{activeResponse.elapsed}ms</span>
|
||||
</>
|
||||
)}
|
||||
{activeResponse.body.length > 0 && (
|
||||
{activeResponse.contentLength && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span>{(activeResponse.body.length / 1000).toFixed(1)} KB</span>
|
||||
<span>{(activeResponse.contentLength / 1000).toFixed(1)} KB</span>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
@@ -169,49 +161,29 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Response"
|
||||
tabs={tabs}
|
||||
className="ml-3 mr-1"
|
||||
tabListClassName="mt-1.5"
|
||||
>
|
||||
<TabContent value="headers">
|
||||
<ResponseHeaders headers={activeResponse?.headers ?? []} />
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
{!activeResponse.body ? (
|
||||
<EmptyStateText>Empty Body</EmptyStateText>
|
||||
) : viewMode === 'pretty' && contentType.includes('html') ? (
|
||||
<Webview
|
||||
body={activeResponse.body}
|
||||
contentType={contentType}
|
||||
url={activeResponse.url}
|
||||
/>
|
||||
) : viewMode === 'pretty' && contentType.includes('json') ? (
|
||||
<Editor
|
||||
readOnly
|
||||
forceUpdateKey={`pretty::${activeResponse.updatedAt}`}
|
||||
className="bg-gray-50 dark:!bg-gray-100"
|
||||
defaultValue={tryFormatJson(activeResponse?.body)}
|
||||
contentType={contentType}
|
||||
/>
|
||||
) : contentType.startsWith('image') ? (
|
||||
<ImageView data={activeResponse?.body} />
|
||||
) : activeResponse?.body ? (
|
||||
<Editor
|
||||
readOnly
|
||||
forceUpdateKey={activeResponse.updatedAt}
|
||||
className="bg-gray-50 dark:!bg-gray-100"
|
||||
defaultValue={activeResponse?.body}
|
||||
contentType={contentType}
|
||||
/>
|
||||
) : null}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Response"
|
||||
tabs={tabs}
|
||||
className="ml-3 mr-1"
|
||||
tabListClassName="mt-1.5"
|
||||
>
|
||||
<TabContent value="headers">
|
||||
<ResponseHeaders headers={activeResponse?.headers ?? []} />
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
{!activeResponse.contentLength ? (
|
||||
<EmptyStateText>Empty Body</EmptyStateText>
|
||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||
<WebPageViewer response={activeResponse} />
|
||||
) : contentType?.startsWith('image') ? (
|
||||
<ImageViewer response={activeResponse} />
|
||||
) : (
|
||||
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
||||
)}
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { Button } from './core/Button';
|
||||
import { Heading } from './core/Heading';
|
||||
import { VStack } from './core/Stacks';
|
||||
@@ -8,6 +9,7 @@ export default function RouteError() {
|
||||
const stringified = JSON.stringify(error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const message = (error as any).message ?? stringified;
|
||||
const routes = useAppRoutes();
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<VStack space={5} className="max-w-[30rem] !h-auto">
|
||||
@@ -16,7 +18,12 @@ export default function RouteError() {
|
||||
{message}
|
||||
</pre>
|
||||
<VStack space={2}>
|
||||
<Button to="/" color="primary">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
routes.navigate('workspaces');
|
||||
}}
|
||||
>
|
||||
Go Home
|
||||
</Button>
|
||||
<Button color="secondary" onClick={() => window.location.reload()}>
|
||||
|
||||
@@ -67,8 +67,8 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
if (hasFocus) return;
|
||||
focusActiveRequest(selectedIndex ?? 0);
|
||||
}, [focusActiveRequest, hasFocus, selectedIndex]);
|
||||
focusActiveRequest();
|
||||
}, [focusActiveRequest, hasFocus]);
|
||||
|
||||
const handleBlur = useCallback(() => setHasFocus(false), []);
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
className="w-8 mr-0.5"
|
||||
icon={loading ? 'update' : 'paperPlane'}
|
||||
spin={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -137,11 +137,11 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
||||
];
|
||||
}, [
|
||||
workspaces,
|
||||
activeWorkspace?.name,
|
||||
deleteWorkspace.mutate,
|
||||
activeWorkspaceId,
|
||||
dialog,
|
||||
routes,
|
||||
prompt,
|
||||
activeWorkspace?.name,
|
||||
updateWorkspace,
|
||||
createWorkspace,
|
||||
]);
|
||||
|
||||
@@ -24,10 +24,10 @@ export interface EditorProps {
|
||||
type?: 'text' | 'password';
|
||||
className?: string;
|
||||
heightMode?: 'auto' | 'full';
|
||||
contentType?: string;
|
||||
contentType?: string | null;
|
||||
forceUpdateKey?: string;
|
||||
autoFocus?: boolean;
|
||||
defaultValue?: string;
|
||||
defaultValue?: string | null;
|
||||
placeholder?: string;
|
||||
tooltipContainer?: HTMLElement;
|
||||
useTemplating?: boolean;
|
||||
|
||||
@@ -32,7 +32,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
||||
) {
|
||||
const [confirmed, setConfirmed] = useTimedBoolean();
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLElement>) => {
|
||||
(e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (showConfirm) setConfirmed();
|
||||
onClick?.(e);
|
||||
},
|
||||
|
||||
15
src-web/components/responseViewers/ImageViewer.tsx
Normal file
15
src-web/components/responseViewers/ImageViewer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function ImageViewer({ response }: Props) {
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
}
|
||||
|
||||
const src = convertFileSrc(response.bodyPath);
|
||||
return <img src={src} alt="Response preview" />;
|
||||
}
|
||||
26
src-web/components/responseViewers/TextViewer.tsx
Normal file
26
src-web/components/responseViewers/TextViewer.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||
import { useResponseContentType } from '../../hooks/useResponseContentType';
|
||||
import { tryFormatJson } from '../../lib/formatters';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
import { Editor } from '../core/Editor';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
pretty: boolean;
|
||||
}
|
||||
|
||||
export function TextViewer({ response, pretty }: Props) {
|
||||
const contentType = useResponseContentType(response);
|
||||
const rawBody = useResponseBodyText(response) ?? '';
|
||||
const body = pretty && contentType?.includes('json') ? tryFormatJson(rawBody) : rawBody;
|
||||
|
||||
return (
|
||||
<Editor
|
||||
readOnly
|
||||
forceUpdateKey={body}
|
||||
className="bg-gray-50 dark:!bg-gray-100"
|
||||
defaultValue={body}
|
||||
contentType={contentType}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
body: string;
|
||||
contentType: string;
|
||||
url: string;
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function Webview({ body, url, contentType }: Props) {
|
||||
export function WebPageViewer({ response }: Props) {
|
||||
const { url } = response;
|
||||
const body = useResponseBodyText(response) ?? '';
|
||||
|
||||
const contentForIframe: string | undefined = useMemo(() => {
|
||||
if (!contentType.includes('html')) return;
|
||||
if (body.includes('<head>')) {
|
||||
return body.replace(/<head>/gi, `<head><base href="${url}"/>`);
|
||||
}
|
||||
return body;
|
||||
}, [url, body, contentType]);
|
||||
}, [url, body]);
|
||||
|
||||
return (
|
||||
<div className="h-full pb-3">
|
||||
@@ -35,6 +35,10 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
if (response.body === null) {
|
||||
return Promise.reject(new Error('Empty body returned in response'));
|
||||
}
|
||||
|
||||
const { data } = JSON.parse(response.body);
|
||||
return buildClientSchema(data);
|
||||
},
|
||||
|
||||
20
src-web/hooks/useResponseBodyBlob.ts
Normal file
20
src-web/hooks/useResponseBodyBlob.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { readBinaryFile } from '@tauri-apps/api/fs';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
|
||||
export function useResponseBodyBlob(response: HttpResponse | null) {
|
||||
return useQuery<Uint8Array | null>({
|
||||
enabled: response != null,
|
||||
queryKey: ['response-body-binary', response?.updatedAt],
|
||||
initialData: null,
|
||||
queryFn: async () => {
|
||||
if (response?.body) {
|
||||
return Uint8Array.of(...response.body);
|
||||
}
|
||||
if (response?.bodyPath) {
|
||||
return readBinaryFile(response.bodyPath);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}).data;
|
||||
}
|
||||
21
src-web/hooks/useResponseBodyText.ts
Normal file
21
src-web/hooks/useResponseBodyText.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { readTextFile } from '@tauri-apps/api/fs';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
|
||||
export function useResponseBodyText(response: HttpResponse) {
|
||||
return useQuery<string | null>({
|
||||
queryKey: ['response-body-text', response?.updatedAt],
|
||||
initialData: null,
|
||||
queryFn: async () => {
|
||||
if (response.body) {
|
||||
return String.fromCharCode.apply(null, response.body);
|
||||
}
|
||||
|
||||
if (response.bodyPath) {
|
||||
return await readTextFile(response.bodyPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}).data;
|
||||
}
|
||||
9
src-web/hooks/useResponseContentType.ts
Normal file
9
src-web/hooks/useResponseContentType.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
|
||||
export function useResponseContentType(response: HttpResponse | null): string | null {
|
||||
return useMemo(
|
||||
() => response?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? null,
|
||||
[response],
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useLocalStorage } from 'react-use';
|
||||
|
||||
export function useResponseViewMode(
|
||||
requestId?: string,
|
||||
): [string | undefined, (m: 'pretty' | 'raw') => void] {
|
||||
const [value, setValue] = useLocalStorage<'pretty' | 'raw'>(
|
||||
`response_view_mode::${requestId}`,
|
||||
'pretty',
|
||||
);
|
||||
return [value, setValue];
|
||||
const DEFAULT_VIEW_MODE = 'pretty';
|
||||
|
||||
export function useResponseViewMode(requestId?: string): [string, (m: 'pretty' | 'raw') => void] {
|
||||
const [value, setValue] = useLocalStorage<'pretty' | 'raw'>(`response_view_mode::${requestId}`);
|
||||
return [value ?? DEFAULT_VIEW_MODE, setValue];
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@ export interface HttpResponse extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly model: 'http_response';
|
||||
readonly requestId: string;
|
||||
readonly body: string;
|
||||
readonly body: number[] | null;
|
||||
readonly bodyPath: string | null;
|
||||
readonly contentLength: number | null;
|
||||
readonly error: string;
|
||||
readonly status: number;
|
||||
readonly elapsed: number;
|
||||
|
||||
Reference in New Issue
Block a user