Compare commits

..

No commits in common. "main" and "v0.3.1" have entirely different histories.
main ... v0.3.1

2 changed files with 61 additions and 98 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@debros/network-ts-sdk", "name": "@debros/network-ts-sdk",
"version": "0.3.4", "version": "0.3.1",
"description": "TypeScript SDK for DeBros Network Gateway", "description": "TypeScript SDK for DeBros Network Gateway",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@ -8,29 +8,6 @@ export interface HttpClientConfig {
fetch?: typeof fetch; fetch?: typeof fetch;
} }
/**
* Create a fetch function with proper TLS configuration for staging certificates
* In Node.js, we need to configure TLS to accept Let's Encrypt staging certificates
*/
function createFetchWithTLSConfig(): typeof fetch {
// Check if we're in a Node.js environment
if (typeof process !== "undefined" && process.versions?.node) {
// For testing/staging/development: allow staging certificates
// Let's Encrypt staging certificates are self-signed and not trusted by default
const isDevelopmentOrStaging =
process.env.NODE_ENV !== "production" ||
process.env.DEBROS_ALLOW_STAGING_CERTS === "true" ||
process.env.DEBROS_USE_HTTPS === "true";
if (isDevelopmentOrStaging) {
// Allow self-signed/staging certificates
// WARNING: Only use this in development/testing environments
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
}
return globalThis.fetch;
}
export class HttpClient { export class HttpClient {
private baseURL: string; private baseURL: string;
private timeout: number; private timeout: number;
@ -45,8 +22,7 @@ export class HttpClient {
this.timeout = config.timeout ?? 60000; // Increased from 30s to 60s for pub/sub operations this.timeout = config.timeout ?? 60000; // Increased from 30s to 60s for pub/sub operations
this.maxRetries = config.maxRetries ?? 3; this.maxRetries = config.maxRetries ?? 3;
this.retryDelayMs = config.retryDelayMs ?? 1000; this.retryDelayMs = config.retryDelayMs ?? 1000;
// Use provided fetch or create one with proper TLS configuration for staging certificates this.fetch = config.fetch ?? globalThis.fetch;
this.fetch = config.fetch ?? createFetchWithTLSConfig();
} }
setApiKey(apiKey?: string) { setApiKey(apiKey?: string) {
@ -159,51 +135,6 @@ export class HttpClient {
fetchOptions.body = JSON.stringify(options.body); fetchOptions.body = JSON.stringify(options.body);
} }
// Extract and log SQL query details for rqlite operations
const isRqliteOperation = path.includes("/v1/rqlite/");
let queryDetails: string | null = null;
if (isRqliteOperation && options.body) {
try {
const body =
typeof options.body === "string"
? JSON.parse(options.body)
: options.body;
if (body.sql) {
// Direct SQL query (query/exec endpoints)
queryDetails = `SQL: ${body.sql}`;
if (body.args && body.args.length > 0) {
queryDetails += ` | Args: [${body.args
.map((a: any) => (typeof a === "string" ? `"${a}"` : a))
.join(", ")}]`;
}
} else if (body.table) {
// Table-based query (find/find-one/select endpoints)
queryDetails = `Table: ${body.table}`;
if (body.criteria && Object.keys(body.criteria).length > 0) {
queryDetails += ` | Criteria: ${JSON.stringify(body.criteria)}`;
}
if (body.options) {
queryDetails += ` | Options: ${JSON.stringify(body.options)}`;
}
if (body.select) {
queryDetails += ` | Select: ${JSON.stringify(body.select)}`;
}
if (body.where) {
queryDetails += ` | Where: ${JSON.stringify(body.where)}`;
}
if (body.limit) {
queryDetails += ` | Limit: ${body.limit}`;
}
if (body.offset) {
queryDetails += ` | Offset: ${body.offset}`;
}
}
} catch (e) {
// Failed to parse body, ignore
}
}
try { try {
const result = await this.requestWithRetry( const result = await this.requestWithRetry(
url.toString(), url.toString(),
@ -213,42 +144,74 @@ export class HttpClient {
); );
const duration = performance.now() - startTime; const duration = performance.now() - startTime;
if (typeof console !== "undefined") { if (typeof console !== "undefined") {
const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed( console.log(
2 `[HttpClient] ${method} ${path} completed in ${duration.toFixed(2)}ms`
)}ms`; );
if (queryDetails) {
console.log(logMessage);
console.log(`[HttpClient] ${queryDetails}`);
} else {
console.log(logMessage);
}
} }
return result; return result;
} catch (error) { } catch (error) {
const duration = performance.now() - startTime; const duration = performance.now() - startTime;
if (typeof console !== "undefined") { if (typeof console !== "undefined") {
// For 404 errors on find-one calls, log at warn level (not error) since "not found" is expected // Cache "key not found" (404 or error message) is expected behavior - don't log as error
// Application layer handles these cases in try-catch blocks const isCacheGetNotFound =
const is404FindOne = path === "/v1/cache/get" &&
error instanceof SDKError &&
(error.httpStatus === 404 ||
(error.httpStatus === 500 &&
error.message?.toLowerCase().includes("key not found")));
// "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" && path === "/v1/rqlite/find-one" &&
error instanceof SDKError && error instanceof SDKError &&
error.httpStatus === 404; 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 (is404FindOne) { // "Not found" (404) for conversation_participants is expected behavior - don't log as error
// Log as warning for visibility, but not as error since it's expected behavior // This happens when checking if a user is a participant (e.g., on first group join)
console.warn( const isConversationParticipantNotFound =
`[HttpClient] ${method} ${path} returned 404 after ${duration.toFixed( path === "/v1/rqlite/find-one" &&
2 error instanceof SDKError &&
)}ms (expected for optional lookups)` error.httpStatus === 404 &&
); options.body &&
(() => {
try {
const body =
typeof options.body === "string"
? JSON.parse(options.body)
: options.body;
return body.table === "conversation_participants";
} catch {
return false;
}
})();
if (
isCacheGetNotFound ||
isBlockedUsersNotFound ||
isConversationParticipantNotFound
) {
// Log cache miss, non-blocked status, or non-participant status as debug/info, not error
// These are expected behaviors
} else { } else {
const errorMessage = `[HttpClient] ${method} ${path} failed after ${duration.toFixed( console.error(
2 `[HttpClient] ${method} ${path} failed after ${duration.toFixed(
)}ms:`; 2
console.error(errorMessage, error); )}ms:`,
if (queryDetails) { error
console.error(`[HttpClient] ${queryDetails}`); );
}
} }
} }
throw error; throw error;