Skip to content

Commit 05de96a

Browse files
fix(api): enforce request id and user-agent headers
1 parent 5c96dfe commit 05de96a

File tree

3 files changed

+49
-6
lines changed

3 files changed

+49
-6
lines changed

.changeset/api-catalog-commerce.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@godaddy/cli": minor
3+
---
4+
5+
Expand the built-in Commerce API catalog with additional domains and GraphQL metadata, and normalize Commerce scope tokens across generated endpoints.
6+
7+
Also improves API command behavior by resolving templated catalog paths (for example, `/stores/{storeId}/...`), validating trusted absolute API hosts, and surfacing richer structured API error details for troubleshooting.

src/core/api.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,41 @@ const MAX_ERROR_SUMMARY_CHARS = 240;
7676
const MAX_ERROR_DEPTH = 6;
7777
const MAX_ERROR_ARRAY_ITEMS = 40;
7878
const MAX_ERROR_OBJECT_KEYS = 80;
79+
const DEFAULT_USER_AGENT = "godaddy-cli";
80+
81+
function findHeaderKey(
82+
headers: Record<string, string>,
83+
headerName: string,
84+
): string | undefined {
85+
const target = headerName.toLowerCase();
86+
return Object.keys(headers).find((key) => key.toLowerCase() === target);
87+
}
88+
89+
function getHeaderValue(
90+
headers: Record<string, string>,
91+
headerName: string,
92+
): string | undefined {
93+
const key = findHeaderKey(headers, headerName);
94+
return key ? headers[key] : undefined;
95+
}
96+
97+
function hasNonEmptyHeader(
98+
headers: Record<string, string>,
99+
headerName: string,
100+
): boolean {
101+
const value = getHeaderValue(headers, headerName);
102+
return typeof value === "string" && value.trim().length > 0;
103+
}
104+
105+
function ensureRequiredRequestHeaders(headers: Record<string, string>): void {
106+
if (!hasNonEmptyHeader(headers, "x-request-id")) {
107+
headers["x-request-id"] = uuid();
108+
}
109+
110+
if (!hasNonEmptyHeader(headers, "user-agent")) {
111+
headers["user-agent"] = DEFAULT_USER_AGENT;
112+
}
113+
}
79114

80115
function truncateString(value: string, maxChars: number): string {
81116
if (value.length <= maxChars) return value;
@@ -454,21 +489,21 @@ export function apiRequestEffect(
454489
// Build headers
455490
const requestHeaders: Record<string, string> = {
456491
Authorization: `Bearer ${accessToken}`,
457-
"X-Request-ID": uuid(),
458492
...headers,
459493
};
494+
ensureRequiredRequestHeaders(requestHeaders);
460495

461496
// Build body
462497
let requestBody: string | undefined;
463498
if (body) {
464499
requestBody = body;
465-
if (!requestHeaders["Content-Type"]) {
466-
requestHeaders["Content-Type"] = "application/json";
500+
if (!hasNonEmptyHeader(requestHeaders, "content-type")) {
501+
requestHeaders["content-type"] = "application/json";
467502
}
468503
} else if (fields && Object.keys(fields).length > 0) {
469504
requestBody = JSON.stringify(fields);
470-
if (!requestHeaders["Content-Type"]) {
471-
requestHeaders["Content-Type"] = "application/json";
505+
if (!hasNonEmptyHeader(requestHeaders, "content-type")) {
506+
requestHeaders["content-type"] = "application/json";
472507
}
473508
}
474509

tests/unit/core/api.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ describe("API Core Functions", () => {
8181
method: "GET",
8282
headers: expect.objectContaining({
8383
Authorization: "Bearer test-token-123",
84-
"X-Request-ID": expect.any(String),
84+
"x-request-id": expect.any(String),
85+
"user-agent": "godaddy-cli",
8586
}),
8687
}),
8788
);

0 commit comments

Comments
 (0)