Improves API client response handling and idempotency
Extends `X-Idempotency-Key` header usage to `DELETE` and `PUT` requests, ensuring idempotent behavior for a wider range of modifying API operations. Updates the documentation and client implementation for `DELETE` requests. Refactors `handleResponse` to robustly parse API responses. It now reads the response body once as text, enabling graceful handling of non-JSON error bodies and consistent processing of successful or empty responses.
This commit is contained in:
parent
28c24d8cb1
commit
02e8c2a89b
@ -610,7 +610,7 @@ Her API isteğine (`login` ve `refresh` dahil) aşağıdaki header'lar eklenir:
|
|||||||
| `X-Signature` | hex string | HMAC-SHA256 imzası (bkz. aşağısı) |
|
| `X-Signature` | hex string | HMAC-SHA256 imzası (bkz. aşağısı) |
|
||||||
| `X-Timestamp` | `Date.now()` string | Unix epoch, milisaniye |
|
| `X-Timestamp` | `Date.now()` string | Unix epoch, milisaniye |
|
||||||
| `X-Nonce` | UUID v4 | Her istekte tekil, replay saldırısı önleme |
|
| `X-Nonce` | UUID v4 | Her istekte tekil, replay saldırısı önleme |
|
||||||
| `X-Idempotency-Key` | UUID v4 | Yalnızca `POST` ve `PATCH` isteklerinde |
|
| `X-Idempotency-Key` | UUID v4 | `POST`, `PUT`, `PATCH` ve `DELETE` isteklerinde |
|
||||||
|
|
||||||
### HMAC-SHA256 İmza Hesabı
|
### HMAC-SHA256 İmza Hesabı
|
||||||
|
|
||||||
@ -664,7 +664,7 @@ X-Signature = HMAC-SHA256(imzalanacak, secret) → "7be41d..."
|
|||||||
| `X-Signature` | ✅ | ✅ | ✅ |
|
| `X-Signature` | ✅ | ✅ | ✅ |
|
||||||
| `X-Timestamp` | ✅ | ✅ | ✅ |
|
| `X-Timestamp` | ✅ | ✅ | ✅ |
|
||||||
| `X-Nonce` | ✅ | ✅ | ✅ |
|
| `X-Nonce` | ✅ | ✅ | ✅ |
|
||||||
| `X-Idempotency-Key` | ✅ | ✅ | ✅ (POST/PATCH) |
|
| `X-Idempotency-Key` | ✅ | ✅ | ✅ (POST/PUT/PATCH/DELETE) |
|
||||||
|
|
||||||
### Güvenlik Önerileri
|
### Güvenlik Önerileri
|
||||||
|
|
||||||
|
|||||||
@ -40,14 +40,24 @@ async function buildHeaders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleResponse<T>(response: Response): Promise<T> {
|
async function handleResponse<T>(response: Response): Promise<T> {
|
||||||
|
const rawBody = await response.text();
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json().catch(() => ({}));
|
let msg = response.statusText;
|
||||||
const msg = (error as { message?: string }).message ?? response.statusText;
|
if (rawBody) {
|
||||||
|
try {
|
||||||
|
const error = JSON.parse(rawBody) as { message?: string };
|
||||||
|
msg = error.message ?? rawBody;
|
||||||
|
} catch {
|
||||||
|
msg = rawBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw new Error(`API error [${response.status}]: ${msg}`);
|
throw new Error(`API error [${response.status}]: ${msg}`);
|
||||||
}
|
}
|
||||||
// 204 No Content
|
|
||||||
if (response.status === 204) return {} as T;
|
if (!rawBody) return {} as T;
|
||||||
return response.json() as Promise<T>;
|
|
||||||
|
return JSON.parse(rawBody) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiGet<T>(
|
export async function apiGet<T>(
|
||||||
@ -98,7 +108,7 @@ export async function apiPatch<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function apiDelete<T>(path: string): Promise<T> {
|
export async function apiDelete<T>(path: string): Promise<T> {
|
||||||
const headers = await buildHeaders("DELETE", path, "", false);
|
const headers = await buildHeaders("DELETE", path, "", true);
|
||||||
|
|
||||||
const response = await fetch(`${config.baseUrl}${path}`, {
|
const response = await fetch(`${config.baseUrl}${path}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user