mirror of
https://github.com/DeBrosOfficial/network-ts-sdk.git
synced 2025-12-14 19:18:49 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d744e26a19 | ||
|
|
681299efdd | ||
|
|
3db9f4d8b8 | ||
|
|
ca81e60bcb | ||
|
|
091a6d5751 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@debros/network-ts-sdk",
|
"name": "@debros/network-ts-sdk",
|
||||||
"version": "0.3.1",
|
"version": "0.3.4",
|
||||||
"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",
|
||||||
|
|||||||
155
src/core/http.ts
155
src/core/http.ts
@ -8,6 +8,29 @@ 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;
|
||||||
@ -22,7 +45,8 @@ 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;
|
||||||
this.fetch = config.fetch ?? globalThis.fetch;
|
// Use provided fetch or create one with proper TLS configuration for staging certificates
|
||||||
|
this.fetch = config.fetch ?? createFetchWithTLSConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
setApiKey(apiKey?: string) {
|
setApiKey(apiKey?: string) {
|
||||||
@ -135,6 +159,51 @@ 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(),
|
||||||
@ -144,74 +213,42 @@ export class HttpClient {
|
|||||||
);
|
);
|
||||||
const duration = performance.now() - startTime;
|
const duration = performance.now() - startTime;
|
||||||
if (typeof console !== "undefined") {
|
if (typeof console !== "undefined") {
|
||||||
console.log(
|
const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed(
|
||||||
`[HttpClient] ${method} ${path} completed in ${duration.toFixed(2)}ms`
|
2
|
||||||
);
|
)}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") {
|
||||||
// Cache "key not found" (404 or error message) is expected behavior - don't log as error
|
// For 404 errors on find-one calls, log at warn level (not error) since "not found" is expected
|
||||||
const isCacheGetNotFound =
|
// Application layer handles these cases in try-catch blocks
|
||||||
path === "/v1/cache/get" &&
|
const is404FindOne =
|
||||||
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;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// "Not found" (404) for conversation_participants is expected behavior - don't log as error
|
if (is404FindOne) {
|
||||||
// This happens when checking if a user is a participant (e.g., on first group join)
|
// Log as warning for visibility, but not as error since it's expected behavior
|
||||||
const isConversationParticipantNotFound =
|
console.warn(
|
||||||
path === "/v1/rqlite/find-one" &&
|
`[HttpClient] ${method} ${path} returned 404 after ${duration.toFixed(
|
||||||
error instanceof SDKError &&
|
|
||||||
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 {
|
|
||||||
console.error(
|
|
||||||
`[HttpClient] ${method} ${path} failed after ${duration.toFixed(
|
|
||||||
2
|
2
|
||||||
)}ms:`,
|
)}ms (expected for optional lookups)`
|
||||||
error
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
const errorMessage = `[HttpClient] ${method} ${path} failed after ${duration.toFixed(
|
||||||
|
2
|
||||||
|
)}ms:`;
|
||||||
|
console.error(errorMessage, error);
|
||||||
|
if (queryDetails) {
|
||||||
|
console.error(`[HttpClient] ${queryDetails}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user