mirror of
https://github.com/DeBrosOfficial/network-ts-sdk.git
synced 2025-12-12 18:28:50 +00:00
Refactor HttpClient to improve API key and JWT handling for various operations
- Removed redundant console logging in setApiKey method. - Updated getAuthHeaders method to include cache operations in API key usage logic. - Enhanced request logging to track request duration and handle expected 404 errors gracefully. - Improved code readability by formatting SQL queries in the Repository class.
This commit is contained in:
parent
64cfe078f0
commit
51f7c433c7
143
src/core/http.ts
143
src/core/http.ts
@ -28,14 +28,6 @@ export class HttpClient {
|
|||||||
setApiKey(apiKey?: string) {
|
setApiKey(apiKey?: string) {
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
// Don't clear JWT - allow both to coexist
|
// Don't clear JWT - allow both to coexist
|
||||||
if (typeof console !== "undefined") {
|
|
||||||
console.log(
|
|
||||||
"[HttpClient] API key set:",
|
|
||||||
!!apiKey,
|
|
||||||
"JWT still present:",
|
|
||||||
!!this.jwt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setJwt(jwt?: string) {
|
setJwt(jwt?: string) {
|
||||||
@ -54,22 +46,39 @@ export class HttpClient {
|
|||||||
private getAuthHeaders(path: string): Record<string, string> {
|
private getAuthHeaders(path: string): Record<string, string> {
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
|
|
||||||
// For database, pubsub, and proxy operations, ONLY use API key to avoid JWT user context
|
// For database, pubsub, proxy, and cache operations, ONLY use API key to avoid JWT user context
|
||||||
// interfering with namespace-level authorization
|
// interfering with namespace-level authorization
|
||||||
const isDbOperation = path.includes("/v1/rqlite/");
|
const isDbOperation = path.includes("/v1/rqlite/");
|
||||||
const isPubSubOperation = path.includes("/v1/pubsub/");
|
const isPubSubOperation = path.includes("/v1/pubsub/");
|
||||||
const isProxyOperation = path.includes("/v1/proxy/");
|
const isProxyOperation = path.includes("/v1/proxy/");
|
||||||
|
const isCacheOperation = path.includes("/v1/cache/");
|
||||||
|
|
||||||
if (isDbOperation || isPubSubOperation || isProxyOperation) {
|
// For auth operations, prefer API key over JWT to ensure proper authentication
|
||||||
// For database/pubsub/proxy operations: use only API key (preferred for namespace operations)
|
const isAuthOperation = path.includes("/v1/auth/");
|
||||||
|
|
||||||
|
if (
|
||||||
|
isDbOperation ||
|
||||||
|
isPubSubOperation ||
|
||||||
|
isProxyOperation ||
|
||||||
|
isCacheOperation
|
||||||
|
) {
|
||||||
|
// For database/pubsub/proxy/cache operations: use only API key (preferred for namespace operations)
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
headers["X-API-Key"] = this.apiKey;
|
headers["X-API-Key"] = this.apiKey;
|
||||||
} else if (this.jwt) {
|
} else if (this.jwt) {
|
||||||
// Fallback to JWT if no API key
|
// Fallback to JWT if no API key
|
||||||
headers["Authorization"] = `Bearer ${this.jwt}`;
|
headers["Authorization"] = `Bearer ${this.jwt}`;
|
||||||
}
|
}
|
||||||
|
} else if (isAuthOperation) {
|
||||||
|
// For auth operations: prefer API key over JWT (auth endpoints should use explicit API key)
|
||||||
|
if (this.apiKey) {
|
||||||
|
headers["X-API-Key"] = this.apiKey;
|
||||||
|
}
|
||||||
|
if (this.jwt) {
|
||||||
|
headers["Authorization"] = `Bearer ${this.jwt}`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For auth/other operations: send both JWT and API key
|
// For other operations: send both JWT and API key
|
||||||
if (this.jwt) {
|
if (this.jwt) {
|
||||||
headers["Authorization"] = `Bearer ${this.jwt}`;
|
headers["Authorization"] = `Bearer ${this.jwt}`;
|
||||||
}
|
}
|
||||||
@ -98,6 +107,7 @@ export class HttpClient {
|
|||||||
timeout?: number; // Per-request timeout override
|
timeout?: number; // Per-request timeout override
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
const startTime = performance.now(); // Track request start time
|
||||||
const url = new URL(this.baseURL + path);
|
const url = new URL(this.baseURL + path);
|
||||||
if (options.query) {
|
if (options.query) {
|
||||||
Object.entries(options.query).forEach(([key, value]) => {
|
Object.entries(options.query).forEach(([key, value]) => {
|
||||||
@ -111,27 +121,6 @@ export class HttpClient {
|
|||||||
...options.headers,
|
...options.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug: Log headers being sent
|
|
||||||
if (
|
|
||||||
typeof console !== "undefined" &&
|
|
||||||
(path.includes("/db/") ||
|
|
||||||
path.includes("/query") ||
|
|
||||||
path.includes("/auth/") ||
|
|
||||||
path.includes("/pubsub/") ||
|
|
||||||
path.includes("/proxy/"))
|
|
||||||
) {
|
|
||||||
console.log("[HttpClient] Request headers for", path, {
|
|
||||||
hasAuth: !!headers["Authorization"],
|
|
||||||
hasApiKey: !!headers["X-API-Key"],
|
|
||||||
authPrefix: headers["Authorization"]
|
|
||||||
? headers["Authorization"].substring(0, 20)
|
|
||||||
: "none",
|
|
||||||
apiKeyPrefix: headers["X-API-Key"]
|
|
||||||
? headers["X-API-Key"].substring(0, 20)
|
|
||||||
: "none",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const requestTimeout = options.timeout ?? this.timeout; // Use override or default
|
const requestTimeout = options.timeout ?? this.timeout; // Use override or default
|
||||||
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
||||||
@ -147,7 +136,60 @@ export class HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.requestWithRetry(url.toString(), fetchOptions);
|
const result = await this.requestWithRetry(
|
||||||
|
url.toString(),
|
||||||
|
fetchOptions,
|
||||||
|
0,
|
||||||
|
startTime
|
||||||
|
);
|
||||||
|
const duration = performance.now() - startTime;
|
||||||
|
if (typeof console !== "undefined") {
|
||||||
|
console.log(
|
||||||
|
`[HttpClient] ${method} ${path} completed in ${duration.toFixed(2)}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
const duration = performance.now() - startTime;
|
||||||
|
if (typeof console !== "undefined") {
|
||||||
|
// Cache "key not found" (404) is expected behavior - don't log as error
|
||||||
|
const isCacheGetNotFound =
|
||||||
|
path === "/v1/cache/get" &&
|
||||||
|
error instanceof SDKError &&
|
||||||
|
error.httpStatus === 404;
|
||||||
|
|
||||||
|
// "Not found" (404) for blocked_users is expected behavior - don't log as error
|
||||||
|
// This happens when checking if users are blocked (most users aren't blocked)
|
||||||
|
const isBlockedUsersNotFound =
|
||||||
|
path === "/v1/rqlite/find-one" &&
|
||||||
|
error instanceof SDKError &&
|
||||||
|
error.httpStatus === 404 &&
|
||||||
|
options.body &&
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
const body =
|
||||||
|
typeof options.body === "string"
|
||||||
|
? JSON.parse(options.body)
|
||||||
|
: options.body;
|
||||||
|
return body.table === "blocked_users";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (isCacheGetNotFound || isBlockedUsersNotFound) {
|
||||||
|
// Log cache miss or non-blocked status as debug/info, not error
|
||||||
|
// These are expected behaviors
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`[HttpClient] ${method} ${path} failed after ${duration.toFixed(
|
||||||
|
2
|
||||||
|
)}ms:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
@ -156,7 +198,8 @@ export class HttpClient {
|
|||||||
private async requestWithRetry(
|
private async requestWithRetry(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestInit,
|
options: RequestInit,
|
||||||
attempt: number = 0
|
attempt: number = 0,
|
||||||
|
startTime?: number // Track start time for timing across retries
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const response = await this.fetch(url, options);
|
const response = await this.fetch(url, options);
|
||||||
@ -185,7 +228,7 @@ export class HttpClient {
|
|||||||
await new Promise((resolve) =>
|
await new Promise((resolve) =>
|
||||||
setTimeout(resolve, this.retryDelayMs * (attempt + 1))
|
setTimeout(resolve, this.retryDelayMs * (attempt + 1))
|
||||||
);
|
);
|
||||||
return this.requestWithRetry(url, options, attempt + 1);
|
return this.requestWithRetry(url, options, attempt + 1, startTime);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -232,6 +275,7 @@ export class HttpClient {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
const startTime = performance.now(); // Track upload start time
|
||||||
const url = new URL(this.baseURL + path);
|
const url = new URL(this.baseURL + path);
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
...this.getAuthHeaders(path),
|
...this.getAuthHeaders(path),
|
||||||
@ -250,7 +294,32 @@ export class HttpClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.requestWithRetry(url.toString(), fetchOptions);
|
const result = await this.requestWithRetry(
|
||||||
|
url.toString(),
|
||||||
|
fetchOptions,
|
||||||
|
0,
|
||||||
|
startTime
|
||||||
|
);
|
||||||
|
const duration = performance.now() - startTime;
|
||||||
|
if (typeof console !== "undefined") {
|
||||||
|
console.log(
|
||||||
|
`[HttpClient] POST ${path} (upload) completed in ${duration.toFixed(
|
||||||
|
2
|
||||||
|
)}ms`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
const duration = performance.now() - startTime;
|
||||||
|
if (typeof console !== "undefined") {
|
||||||
|
console.error(
|
||||||
|
`[HttpClient] POST ${path} (upload) failed after ${duration.toFixed(
|
||||||
|
2
|
||||||
|
)}ms:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,9 @@ export class Repository<T extends Record<string, any>> {
|
|||||||
private buildInsertSql(entity: T): string {
|
private buildInsertSql(entity: T): string {
|
||||||
const columns = Object.keys(entity).filter((k) => entity[k] !== undefined);
|
const columns = Object.keys(entity).filter((k) => entity[k] !== undefined);
|
||||||
const placeholders = columns.map(() => "?").join(", ");
|
const placeholders = columns.map(() => "?").join(", ");
|
||||||
return `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders})`;
|
return `INSERT INTO ${this.tableName} (${columns.join(
|
||||||
|
", "
|
||||||
|
)}) VALUES (${placeholders})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildInsertArgs(entity: T): any[] {
|
private buildInsertArgs(entity: T): any[] {
|
||||||
@ -111,7 +113,9 @@ export class Repository<T extends Record<string, any>> {
|
|||||||
const columns = Object.keys(entity)
|
const columns = Object.keys(entity)
|
||||||
.filter((k) => entity[k] !== undefined && k !== this.primaryKey)
|
.filter((k) => entity[k] !== undefined && k !== this.primaryKey)
|
||||||
.map((k) => `${k} = ?`);
|
.map((k) => `${k} = ?`);
|
||||||
return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${this.primaryKey} = ?`;
|
return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${
|
||||||
|
this.primaryKey
|
||||||
|
} = ?`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildUpdateArgs(entity: T): any[] {
|
private buildUpdateArgs(entity: T): any[] {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user