mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-22 23:08:53 +02:00
fix(api): return JSON responses for admin settings + API key endpoints to prevent Tauri client parse errors (#5437)
# Description of Changes
```console
index-DsORDqQQ.js:124 \n [TauriHttpClient] Network error: \n{url: 'http://localhost:8080/api/v1/admin/settings', method: 'PUT', errorType: 'ERR_NETWORK', originalMessage: `Failed to execute 'close' on 'ReadableStreamDefaul…cted token 'S', "Successful"... is not valid JSON`, stack: `SyntaxError: Unexpected token 'S', "Successful"...…lback (<anonymous>:284:7)\n at <anonymous>:1:28`}\nerrorType\n: \n"ERR_NETWORK"\nmethod\n: \n"PUT"\noriginalMessage\n: \n"Failed to execute 'close' on 'ReadableStreamDefaultController': Unexpected token 'S', \"Successful\"... is not valid JSON"\nstack\n: \n"SyntaxError: Unexpected token 'S', \"Successful\"... is not valid JSON\n at A.onmessage (http://tauri.localhost/assets/index-DsORDqQQ.js:124:22714)\n at http://tauri.localhost/assets/index-DsORDqQQ.js:124:20748\n at <anonymous>:272:26\n at Object.runCallback (<anonymous>:284:7)\n at <anonymous>:1:28"\nurl\n: \n"http://localhost:8080/api/v1/admin/settings"
index-DXbk7lbS.js:124 \n [TauriHttpClient] Network error: \n{url: 'http://localhost:8080/api/v1/user/get-api-key', method: 'POST', errorType: 'ERR_NETWORK', originalMessage: `Failed to execute 'close' on 'ReadableStreamDefaul…cted token 'a', "a72f6b26-1"... is not valid JSON`, stack: `SyntaxError: Unexpected token 'a', "a72f6b26-1"...…lback (<anonymous>:284:7)\n at <anonymous>:1:28`}\nerrorType\n: \n"ERR_NETWORK"\nmethod\n: \n"POST"\noriginalMessage\n: \n"Failed to execute 'close' on 'ReadableStreamDefaultController': Unexpected token 'a', \"a72f6b26-1\"... is not valid JSON"\nstack\n: \n"SyntaxError: Unexpected token 'a', \"a72f6b26-1\"... is not valid JSON\n at A.onmessage (http://tauri.localhost/assets/index-DXbk7lbS.js:124:22714)\n at http://tauri.localhost/assets/index-DXbk7lbS.js:124:20748\n at <anonymous>:272:26\n at Object.runCallback (<anonymous>:284:7)\n at <anonymous>:1:28"\nurl\n: \n"http://localhost:8080/api/v1/user/get-api-key"
```
This pull request fixes a self-hosting issue where the Tauri HTTP client
fails with `Unexpected token ... is not valid JSON` because certain API
endpoints returned plain text responses.
## What was changed
- Updated `AdminSettingsController`:
- Changed `updateSettings` and `updateSettingsSection` to return
structured JSON objects instead of raw strings.
- Standardized success and error payloads using a `Map<String, Object>`
with keys like `message` and `error`.
- Updated `UserController`:
- Changed `/api/v1/user/get-api-key` and `/api/v1/user/update-api-key`
to return JSON objects (`{ "apiKey": "..." }`) and JSON error objects
instead of plain text.
## Why the change was made
- The Tauri client expects JSON responses and attempts to parse them.
Returning plain strings like `"Successful..."` or an API key string
causes JSON parsing to fail, resulting in network errors on self-hosted
setups.
---
## Checklist
### General
- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### Translations (if applicable)
- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
---------
Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import apiClient from "@app/services/apiClient";
|
||||
import { alert } from "@app/components/toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function useApiKey() {
|
||||
const [apiKey, setApiKey] = useState<string | null>(null);
|
||||
@@ -7,49 +9,107 @@ export function useApiKey() {
|
||||
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [hasAttempted, setHasAttempted] = useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
function failedToCreateAlert() {
|
||||
alert({
|
||||
alertType: "error",
|
||||
title: t("config.apiKeys.alert.apiKeyErrorTitle", "API Key Error"),
|
||||
body: t("config.apiKeys.alert.failedToCreateApiKey", "Failed to create API key."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchKey = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
// Backend is POST for get and update
|
||||
const res = await apiClient.post("/api/v1/user/get-api-key");
|
||||
const value = typeof res.data === "string" ? res.data : res.data?.apiKey;
|
||||
if (typeof value === "string") setApiKey(value);
|
||||
} catch (e: any) {
|
||||
// If not found, try to create one by calling update endpoint
|
||||
if (e?.response?.status === 404) {
|
||||
try {
|
||||
const createRes = await apiClient.post("/api/v1/user/update-api-key");
|
||||
const created =
|
||||
typeof createRes.data === "string"
|
||||
? createRes.data
|
||||
: createRes.data?.apiKey;
|
||||
if (typeof created === "string") setApiKey(created);
|
||||
} catch (createErr: any) {
|
||||
setError(createErr);
|
||||
// Backend is POST for get and update
|
||||
await apiClient
|
||||
.post("/api/v1/user/get-api-key", undefined, {
|
||||
responseType: "json",
|
||||
})
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
const apiKeyValue = typeof data === "string" ? data : data?.apiKey;
|
||||
if (typeof apiKeyValue === "string") {
|
||||
setApiKey(apiKeyValue);
|
||||
} else {
|
||||
alert({
|
||||
alertType: "error",
|
||||
title: t("config.apiKeys.alert.apiKeyErrorTitle", "API Key Error"),
|
||||
body: t("config.apiKeys.alert.failedToRetrieveApiKey", "Failed to retrieve API key from response."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setError(e);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setHasAttempted(true);
|
||||
}
|
||||
})
|
||||
.catch(async (e) => {
|
||||
// If not found, try to create one by calling update endpoint
|
||||
if (e?.response?.status === 404) {
|
||||
await apiClient
|
||||
.post("/api/v1/user/update-api-key")
|
||||
.then((createRes) => {
|
||||
const created = typeof createRes.data === "string" ? createRes.data : createRes.data?.apiKey;
|
||||
if (typeof created === "string") {
|
||||
setApiKey(created);
|
||||
} else {
|
||||
failedToCreateAlert();
|
||||
}
|
||||
})
|
||||
.catch((createErr) => {
|
||||
failedToCreateAlert();
|
||||
setError(createErr);
|
||||
});
|
||||
} else {
|
||||
alert({
|
||||
alertType: "error",
|
||||
title: t("config.apiKeys.alert.apiKeyErrorTitle", "API Key Error"),
|
||||
body: t("config.apiKeys.alert.failedToFetchApiKey", "Failed to fetch API key."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
setError(e);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
setHasAttempted(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await apiClient.post("/api/v1/user/update-api-key");
|
||||
await apiClient.post("/api/v1/user/update-api-key", undefined, {
|
||||
responseType: "json",
|
||||
suppressErrorToast: true,
|
||||
}).then((res) => {
|
||||
const value = typeof res.data === "string" ? res.data : res.data?.apiKey;
|
||||
if (typeof value === "string") setApiKey(value);
|
||||
} catch (e: any) {
|
||||
if (typeof value === "string") {
|
||||
alert({
|
||||
alertType: "success",
|
||||
title: t("config.apiKeys.alert.apiKeyRefreshed", "API Key Refreshed"),
|
||||
body: t("config.apiKeys.alert.apiKeyRefreshedBody", "Your API key has been successfully refreshed."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
setApiKey(value);
|
||||
} else {
|
||||
alert({
|
||||
alertType: "error",
|
||||
title: t("config.apiKeys.alert.apiKeyErrorTitle", "API Key Error"),
|
||||
body: t("config.apiKeys.alert.failedToRefreshApiKey", "Failed to refresh API key."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
}
|
||||
}).catch((e) => {
|
||||
alert({
|
||||
alertType: "error",
|
||||
title: t("config.apiKeys.alert.apiKeyErrorTitle", "API Key Error"),
|
||||
body: t("config.apiKeys.alert.failedToRefreshApiKey", "Failed to refresh API key."),
|
||||
isPersistentPopup: false,
|
||||
});
|
||||
setError(e);
|
||||
} finally {
|
||||
}).finally(() => {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user