diff --git a/sdk/typescript/src/api/graphql.ts b/sdk/typescript/src/api/graphql.ts new file mode 100644 index 0000000..f8e01e5 --- /dev/null +++ b/sdk/typescript/src/api/graphql.ts @@ -0,0 +1,18 @@ +import { HTTPClient } from './http-client'; + +export class GraphQLAPI { + constructor(private client: HTTPClient) {} + + async execute(query: string, variables?: Record): Promise { + const data = { query }; + if (variables) { + (data as any).variables = variables; + } + + return this.client.request({ + method: 'POST', + endpoint: '/graphql', + data, + }); + } +} diff --git a/sdk/typescript/src/api/http-client.ts b/sdk/typescript/src/api/http-client.ts new file mode 100644 index 0000000..a45b3dc --- /dev/null +++ b/sdk/typescript/src/api/http-client.ts @@ -0,0 +1,109 @@ +import { AuthProvider } from '../auth'; + +export interface RequestConfig { + method: string; + endpoint: string; + data?: any; + params?: Record; +} + +export class HTTPClient { + private baseURL: string; + private authProvider: AuthProvider; + private timeout: number; + private maxRetries: number; + private retryDelay: number; + private enableLogging: boolean; + + constructor( + baseURL: string, + authProvider: AuthProvider, + timeout: number = 30000, + maxRetries: number = 3, + retryDelay: number = 1000, + enableLogging: boolean = false + ) { + this.baseURL = baseURL.replace(/\/$/, ''); + this.authProvider = authProvider; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.retryDelay = retryDelay; + this.enableLogging = enableLogging; + } + + async request(config: RequestConfig): Promise { + const { method, endpoint, data, params } = config; + const url = new URL(`${this.baseURL}${endpoint}`); + + if (params) { + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + } + + const headers = this.authProvider.getHeaders(); + const options: RequestInit = { + method, + headers, + }; + + if (data && method !== 'GET') { + options.body = JSON.stringify(data); + } + + let lastError: Error | null = null; + + for (let attempt = 0; attempt <= this.maxRetries; attempt++) { + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.timeout); + + const response = await fetch(url.toString(), { + ...options, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + await this.handleResponseErrors(response); + + return await response.json(); + } catch (error) { + lastError = error as Error; + + if (attempt === this.maxRetries) { + throw new Error(`Request failed after ${this.maxRetries} retries: ${lastError.message}`); + } + + if (this.enableLogging) { + console.log(`Request failed (attempt ${attempt + 1}), retrying in ${this.retryDelay}ms: ${lastError.message}`); + } + + await this.delay(this.retryDelay * Math.pow(2, attempt)); + } + } + + throw lastError; + } + + private async handleResponseErrors(response: Response): Promise { + if (response.status === 401) { + throw new Error('Authentication failed'); + } else if (response.status === 429) { + throw new Error('Rate limit exceeded'); + } else if (response.status >= 400 && response.status < 500) { + const errorData = await response.json().catch(() => ({})); + throw new Error(`API error: ${errorData.error || 'Bad request'}`); + } else if (response.status >= 500) { + throw new Error(`Server error: ${response.status}`); + } + } + + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + close(): void { + // Cleanup if needed + } +} diff --git a/sdk/typescript/src/api/index.ts b/sdk/typescript/src/api/index.ts new file mode 100644 index 0000000..4c88918 --- /dev/null +++ b/sdk/typescript/src/api/index.ts @@ -0,0 +1,7 @@ +export { HTTPClient } from './http-client'; +export { SubscriberAPI } from './subscribers'; +export { UsageAPI } from './usage'; +export { PaymentAPI } from './payments'; +export { RatingPlanAPI } from './rating-plans'; +export { SystemAPI } from './system'; +export { GraphQLAPI } from './graphql'; diff --git a/sdk/typescript/src/api/payments.ts b/sdk/typescript/src/api/payments.ts new file mode 100644 index 0000000..66a1664 --- /dev/null +++ b/sdk/typescript/src/api/payments.ts @@ -0,0 +1,90 @@ +import { HTTPClient } from './http-client'; +import { Invoice, PaymentMethod, PaginatedResponse } from '../types'; + +export class PaymentAPI { + constructor(private client: HTTPClient) {} + + async createTransaction(data: { + subscriberId: number; + amount: number; + currency?: string; + gateway?: string; + metadata?: Record; + }): Promise { + return this.client.request({ + method: 'POST', + endpoint: '/v1/payments/transactions', + data, + }); + } + + async getTransaction(transactionId: string): Promise { + return this.client.request({ + method: 'GET', + endpoint: `/v1/payments/transactions/${transactionId}`, + }); + } + + async listTransactions( + subscriberId?: number, + status?: string, + page: number = 1, + pageSize: number = 50 + ): Promise> { + const params: Record = { + page: page.toString(), + page_size: pageSize.toString(), + }; + + if (subscriberId) { + params.subscriber_id = subscriberId.toString(); + } + if (status) { + params.status = status; + } + + return this.client.request>({ + method: 'GET', + endpoint: '/v1/payments/transactions', + params, + }); + } + + async listInvoices( + subscriberId?: number, + status?: string, + page: number = 1, + pageSize: number = 50 + ): Promise> { + const params: Record = { + page: page.toString(), + page_size: pageSize.toString(), + }; + + if (subscriberId) { + params.subscriber_id = subscriberId.toString(); + } + if (status) { + params.status = status; + } + + return this.client.request>({ + method: 'GET', + endpoint: '/v1/payments/invoices', + params, + }); + } + + async addPaymentMethod(data: { + subscriberId: number; + type: string; + token: string; + isDefault?: boolean; + }): Promise { + return this.client.request({ + method: 'POST', + endpoint: '/v1/payments/methods', + data, + }); + } +} diff --git a/sdk/typescript/src/api/rating-plans.ts b/sdk/typescript/src/api/rating-plans.ts new file mode 100644 index 0000000..9345fa4 --- /dev/null +++ b/sdk/typescript/src/api/rating-plans.ts @@ -0,0 +1,20 @@ +import { HTTPClient } from './http-client'; +import { RatingPlan } from '../types'; + +export class RatingPlanAPI { + constructor(private client: HTTPClient) {} + + async list(): Promise { + return this.client.request({ + method: 'GET', + endpoint: '/v1/rating-plans', + }); + } + + async get(planId: string): Promise { + return this.client.request({ + method: 'GET', + endpoint: `/v1/rating-plans/${planId}`, + }); + } +} diff --git a/sdk/typescript/src/api/subscribers.ts b/sdk/typescript/src/api/subscribers.ts new file mode 100644 index 0000000..6535a4e --- /dev/null +++ b/sdk/typescript/src/api/subscribers.ts @@ -0,0 +1,79 @@ +import { HTTPClient } from './http-client'; +import { Subscriber, PaginatedResponse } from '../types'; + +export class SubscriberAPI { + constructor(private client: HTTPClient) {} + + async get(subscriberId: number): Promise { + return this.client.request({ + method: 'GET', + endpoint: `/v1/subscribers/${subscriberId}`, + }); + } + + async list( + page: number = 1, + pageSize: number = 50, + status?: string + ): Promise> { + const params: Record = { + page: page.toString(), + page_size: pageSize.toString(), + }; + + if (status) { + params.status = status; + } + + return this.client.request>({ + method: 'GET', + endpoint: '/v1/subscribers', + params, + }); + } + + async create(data: { + imsi: string; + msisdn: string; + firstName: string; + lastName: string; + email: string; + planId: number; + organizationId?: string; + }): Promise { + return this.client.request({ + method: 'POST', + endpoint: '/v1/subscribers', + data, + }); + } + + async update(subscriberId: number, data: Record): Promise { + return this.client.request({ + method: 'PUT', + endpoint: `/v1/subscribers/${subscriberId}`, + data, + }); + } + + async suspend(subscriberId: number): Promise { + return this.client.request({ + method: 'POST', + endpoint: `/v1/subscribers/${subscriberId}/suspend`, + }); + } + + async activate(subscriberId: number): Promise { + return this.client.request({ + method: 'POST', + endpoint: `/v1/subscribers/${subscriberId}/activate`, + }); + } + + async terminate(subscriberId: number): Promise<{ success: boolean }> { + return this.client.request<{ success: boolean }>({ + method: 'DELETE', + endpoint: `/v1/subscribers/${subscriberId}`, + }); + } +} diff --git a/sdk/typescript/src/api/system.ts b/sdk/typescript/src/api/system.ts new file mode 100644 index 0000000..f991bd5 --- /dev/null +++ b/sdk/typescript/src/api/system.ts @@ -0,0 +1,20 @@ +import { HTTPClient } from './http-client'; +import { SystemStats, HealthStatus } from '../types'; + +export class SystemAPI { + constructor(private client: HTTPClient) {} + + async getStats(): Promise { + return this.client.request({ + method: 'GET', + endpoint: '/v1/system/stats', + }); + } + + async getHealth(): Promise { + return this.client.request({ + method: 'GET', + endpoint: '/v1/health', + }); + } +} diff --git a/sdk/typescript/src/api/usage.ts b/sdk/typescript/src/api/usage.ts new file mode 100644 index 0000000..ad35848 --- /dev/null +++ b/sdk/typescript/src/api/usage.ts @@ -0,0 +1,63 @@ +import { HTTPClient } from './http-client'; +import { UsageEvent, RealTimeUsageUpdate, PaginatedResponse, SubscriberAccount } from '../types'; + +export class UsageAPI { + constructor(private client: HTTPClient) {} + + async getStats( + subscriberId: number, + startDate: string, + endDate: string + ): Promise { + const params = { + start_date: startDate, + end_date: endDate, + }; + + return this.client.request({ + method: 'GET', + endpoint: `/v1/subscribers/${subscriberId}/usage`, + params, + }); + } + + async listEvents( + subscriberId?: number, + usageType?: string, + startDate?: string, + endDate?: string, + page: number = 1, + pageSize: number = 50 + ): Promise> { + const params: Record = { + page: page.toString(), + page_size: pageSize.toString(), + }; + + if (subscriberId) { + params.subscriber_id = subscriberId.toString(); + } + if (usageType) { + params.usage_type = usageType; + } + if (startDate) { + params.start_date = startDate; + } + if (endDate) { + params.end_date = endDate; + } + + return this.client.request>({ + method: 'GET', + endpoint: '/v1/usage/events', + params, + }); + } + + async getRealtime(subscriberId: number): Promise { + return this.client.request({ + method: 'GET', + endpoint: `/v1/subscribers/${subscriberId}/realtime`, + }); + } +} diff --git a/sdk/typescript/src/auth.ts b/sdk/typescript/src/auth.ts new file mode 100644 index 0000000..f3bbcdc --- /dev/null +++ b/sdk/typescript/src/auth.ts @@ -0,0 +1,149 @@ +export interface AuthConfig { + apiKey?: string; + jwtSecret?: string; + jwtAlgorithm?: string; +} + +export interface JWTClaims { + sub: string; + exp: number; + iat: number; + [key: string]: any; +} + +export class AuthProvider { + private apiKey: string | undefined; + private jwtSecret: string | undefined; + private jwtAlgorithm: string; + private tokenCache: string | undefined; + private tokenExpiry: number | undefined; + + constructor(config: AuthConfig = {}) { + this.apiKey = config.apiKey; + this.jwtSecret = config.jwtSecret; + this.jwtAlgorithm = config.jwtAlgorithm || 'HS256'; + } + + getHeaders(): Record { + const headers: Record = { + 'Content-Type': 'application/json', + 'User-Agent': 'Telecom-TypeScript-SDK/1.0.0', + }; + + if (this.apiKey) { + headers['X-API-Key'] = this.apiKey; + } + + if (this.tokenCache && this.isTokenValid()) { + headers['Authorization'] = `Bearer ${this.tokenCache}`; + } + + return headers; + } + + generateJWTToken( + userId: string, + expiryHours: number = 24, + additionalClaims: Record = {} + ): string { + if (!this.jwtSecret) { + throw new Error('JWT secret not configured'); + } + + const now = Math.floor(Date.now() / 1000); + const claims: JWTClaims = { + sub: userId, + exp: now + expiryHours * 3600, + iat: now, + ...additionalClaims, + }; + + const header = { + alg: this.jwtAlgorithm, + typ: 'JWT', + }; + + const encodedHeader = this.base64UrlEncode(JSON.stringify(header)); + const encodedPayload = this.base64UrlEncode(JSON.stringify(claims)); + const signature = this.sign(encodedHeader + '.' + encodedPayload, this.jwtSecret); + + const token = `${encodedHeader}.${encodedPayload}.${signature}`; + this.tokenCache = token; + this.tokenExpiry = claims.exp; + + return token; + } + + validateJWTToken(token: string): JWTClaims { + if (!this.jwtSecret) { + throw new Error('JWT secret not configured'); + } + + const parts = token.split('.'); + if (parts.length !== 3) { + throw new Error('Invalid token format'); + } + + const [encodedHeader, encodedPayload, signature] = parts; + const expectedSignature = this.sign(encodedHeader + '.' + encodedPayload, this.jwtSecret); + + if (signature !== expectedSignature) { + throw new Error('Invalid token signature'); + } + + const payload: JWTClaims = JSON.parse(this.base64UrlDecode(encodedPayload)); + + if (payload.exp < Math.floor(Date.now() / 1000)) { + throw new Error('Token has expired'); + } + + return payload; + } + + clearTokenCache(): void { + this.tokenCache = undefined; + this.tokenExpiry = undefined; + } + + private isTokenValid(): boolean { + if (!this.tokenCache || !this.tokenExpiry) { + return false; + } + return this.tokenExpiry > Math.floor(Date.now() / 1000); + } + + private base64UrlEncode(str: string): string { + return btoa(str) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + } + + private base64UrlDecode(str: string): string { + let base64 = str.replace(/-/g, '+').replace(/_/g, '/'); + while (base64.length % 4) { + base64 += '='; + } + return atob(base64); + } + + private sign(data: string, secret: string): string { + // Simple HMAC-SHA256 implementation + // In production, use a proper crypto library like crypto-js or Web Crypto API + const hmac = this.simpleHMACSHA256(data, secret); + return this.base64UrlEncode(hmac); + } + + private simpleHMACSHA256(message: string, key: string): string { + // This is a simplified implementation for demonstration + // In production, use a proper crypto library + let hash = 0; + const str = message + key; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return Math.abs(hash).toString(16).padStart(64, '0'); + } +} diff --git a/sdk/typescript/src/telecom-sdk.ts b/sdk/typescript/src/telecom-sdk.ts index 6579b62..b36758a 100644 --- a/sdk/typescript/src/telecom-sdk.ts +++ b/sdk/typescript/src/telecom-sdk.ts @@ -1,35 +1,85 @@ -import { SDKConfig, TelecomSDKConfig } from './config'; -import { HttpClient } from './http-client'; -import { SubscribersService } from './subscribers'; -import { WebSocketClient, WebSocketEventHandler } from './websocket'; -import { SystemStats, HealthStatus, Alert, RealTimeUsageUpdate, WebSocketMessage } from './types'; +import { AuthProvider } from './auth'; +import { + HTTPClient, + SubscriberAPI, + UsageAPI, + PaymentAPI, + RatingPlanAPI, + SystemAPI, + GraphQLAPI +} from './api'; +import { WebSocketClient } from './websocket'; +import { Alert, RealTimeUsageUpdate, WebSocketMessage } from './types'; export class TelecomSDK { private static instance: TelecomSDK; - private http: HttpClient; + private authProvider: AuthProvider; + private apiClient: HTTPClient; private ws: WebSocketClient; - private subscribers: SubscribersService; + + // API modules + public subscribers: SubscriberAPI; + public usage: UsageAPI; + public payments: PaymentAPI; + public ratingPlans: RatingPlanAPI; + public system: SystemAPI; + public graphql: GraphQLAPI; - private constructor() { - this.http = new HttpClient(); - this.ws = new WebSocketClient(); - this.subscribers = new SubscribersService(this.http); + private constructor(config: { + baseURL: string; + apiKey?: string; + jwtSecret?: string; + timeout?: number; + maxRetries?: number; + retryDelay?: number; + enableLogging?: boolean; + websocketURL?: string; + }) { + const authConfig: any = {}; + if (config.apiKey !== undefined) authConfig.apiKey = config.apiKey; + if (config.jwtSecret !== undefined) authConfig.jwtSecret = config.jwtSecret; + + this.authProvider = new AuthProvider(authConfig); + + this.apiClient = new HTTPClient( + config.baseURL, + this.authProvider, + config.timeout || 30000, + config.maxRetries || 3, + config.retryDelay || 1000, + config.enableLogging || false + ); + + this.ws = new WebSocketClient( + config.websocketURL || config.baseURL.replace('http', 'ws'), + config.enableLogging || false + ); + + // Initialize API modules + this.subscribers = new SubscriberAPI(this.apiClient); + this.usage = new UsageAPI(this.apiClient); + this.payments = new PaymentAPI(this.apiClient); + this.ratingPlans = new RatingPlanAPI(this.apiClient); + this.system = new SystemAPI(this.apiClient); + this.graphql = new GraphQLAPI(this.apiClient); } - /** - * Initialize the SDK with configuration - */ - static initialize(config: TelecomSDKConfig): TelecomSDK { + static initialize(config: { + baseURL: string; + apiKey?: string; + jwtSecret?: string; + timeout?: number; + maxRetries?: number; + retryDelay?: number; + enableLogging?: boolean; + websocketURL?: string; + }): TelecomSDK { if (!TelecomSDK.instance) { - SDKConfig.initialize(config); - TelecomSDK.instance = new TelecomSDK(); + TelecomSDK.instance = new TelecomSDK(config); } return TelecomSDK.instance; } - /** - * Get the singleton instance - */ static getInstance(): TelecomSDK { if (!TelecomSDK.instance) { throw new Error('TelecomSDK not initialized. Call TelecomSDK.initialize() first.'); @@ -37,165 +87,50 @@ export class TelecomSDK { return TelecomSDK.instance; } - /** - * Subscribers service - */ - get subscribersService(): SubscribersService { - return this.subscribers; + // Authentication methods + generateJWTToken(userId: string, expiryHours: number = 24, additionalClaims: Record = {}): string { + return this.authProvider.generateJWTToken(userId, expiryHours, additionalClaims); } - /** - * Connect to WebSocket for real-time updates - */ + validateJWTToken(token: string): any { + return this.authProvider.validateJWTToken(token); + } + + // WebSocket methods async connectWebSocket(): Promise { await this.ws.connect(); } - /** - * Disconnect from WebSocket - */ disconnectWebSocket(): void { this.ws.disconnect(); } - /** - * Check if WebSocket is connected - */ get isWebSocketConnected(): boolean { return this.ws.isConnected; } - /** - * Subscribe to real-time usage updates - */ - subscribeToUsage(imsi: string): void { - this.ws.subscribeToUsage(imsi); - } - - /** - * Unsubscribe from usage updates - */ - unsubscribeFromUsage(imsi: string): void { - this.ws.unsubscribeFromUsage(imsi); - } - - /** - * Subscribe to alerts - */ - subscribeToAlerts(): void { - this.ws.subscribeToAlerts(); - } - - /** - * Unsubscribe from alerts - */ - unsubscribeFromAlerts(): void { - this.ws.unsubscribeFromAlerts(); - } - - /** - * Add WebSocket event handler - */ - onWebSocketEvent(eventType: string, handler: WebSocketEventHandler): void { - this.ws.on(eventType, handler); - } - - /** - * Remove WebSocket event handler - */ - offWebSocketEvent(eventType: string, handler: WebSocketEventHandler): void { - this.ws.off(eventType, handler); - } - - /** - * Get system statistics - */ - async getSystemStats(): Promise { - return this.http.get('/api/system/stats'); - } - - /** - * Get system health status - */ - async getHealthStatus(): Promise { - return this.http.get('/api/system/health'); - } - - /** - * Test API connection - */ - async testConnection(): Promise<{ status: string; timestamp: string }> { - return this.http.get('/api/test'); - } - - /** - * Update API key - */ - setApiKey(apiKey: string): void { - this.http.setApiKey(apiKey); - } - - /** - * Update base URL - */ - setBaseURL(baseURL: string): void { - this.http.setBaseURL(baseURL); - } - - /** - * Enable/disable logging - */ - setLogging(enabled: boolean): void { - SDKConfig.getInstance().updateConfig({ enableLogging: enabled }); - } - - /** - * Get current configuration - */ - getConfig(): TelecomSDKConfig { - const config = SDKConfig.getInstance(); - const result: TelecomSDKConfig = { - baseURL: config.baseURL, - timeout: config.timeout, - retryAttempts: config.retryAttempts, - retryDelay: config.retryDelay, - enableLogging: config.enableLogging, - }; - if (config.apiKey !== undefined) result.apiKey = config.apiKey; - if (config.websocketURL !== undefined) result.websocketURL = config.websocketURL; - return result; - } - - /** - * Convenience method for usage updates - */ + // Convenience methods onUsageUpdate(callback: (update: RealTimeUsageUpdate) => void): void { - this.onWebSocketEvent('usage_update', (message: WebSocketMessage) => { + this.ws.on('usage_update', (message: WebSocketMessage) => { callback(message.data as RealTimeUsageUpdate); }); } - /** - * Convenience method for alerts - */ onAlert(callback: (alert: Alert) => void): void { - this.onWebSocketEvent('alert', (message: WebSocketMessage) => { + this.ws.on('alert', (message: WebSocketMessage) => { callback(message.data as Alert); }); } - /** - * Cleanup resources - */ + // Cleanup destroy(): void { this.disconnectWebSocket(); - // Clear any other resources if needed + this.apiClient.close(); } } // Export types for external use export * from './types'; -export * from './config'; +export { AuthProvider, AuthConfig, JWTClaims } from './auth'; +export * from './api'; export { WebSocketClient } from './websocket'; -export { HttpClient } from './http-client'; -export { SubscribersService } from './subscribers'; diff --git a/sdk/typescript/src/websocket.ts b/sdk/typescript/src/websocket.ts index 5e79e7a..b099895 100644 --- a/sdk/typescript/src/websocket.ts +++ b/sdk/typescript/src/websocket.ts @@ -1,20 +1,21 @@ -import { SDKConfig } from './config'; import { WebSocketMessage } from './types'; export type WebSocketEventHandler = (message: WebSocketMessage) => void; export class WebSocketClient { private ws: WebSocket | null = null; - private config: SDKConfig; private eventHandlers: Map = new Map(); private reconnectAttempts = 0; private maxReconnectAttempts = 5; private reconnectDelay = 1000; private isConnecting = false; private pingInterval: ReturnType | null = null; + private websocketURL: string; + private enableLogging: boolean; - constructor() { - this.config = SDKConfig.getInstance(); + constructor(websocketURL: string, enableLogging: boolean = false) { + this.websocketURL = websocketURL; + this.enableLogging = enableLogging; } /** @@ -25,14 +26,10 @@ export class WebSocketClient { return; } - if (!this.config.websocketURL) { - throw new Error('WebSocket URL not configured'); - } - this.isConnecting = true; try { - this.ws = new WebSocket(this.config.websocketURL); + this.ws = new WebSocket(this.websocketURL); await new Promise((resolve, reject) => { if (!this.ws) return reject(new Error('WebSocket not initialized')); @@ -41,7 +38,7 @@ export class WebSocketClient { this.isConnecting = false; this.reconnectAttempts = 0; this.startPing(); - if (this.config.enableLogging) { + if (this.enableLogging) { console.log('[SDK] WebSocket connected'); } resolve(); @@ -54,7 +51,7 @@ export class WebSocketClient { ); this.handleMessage(message); } catch (error) { - if (this.config.enableLogging) { + if (this.enableLogging) { console.error('[SDK] Failed to parse WebSocket message:', error); } } @@ -63,7 +60,7 @@ export class WebSocketClient { this.ws.onclose = (event: CloseEvent) => { this.isConnecting = false; this.stopPing(); - if (this.config.enableLogging) { + if (this.enableLogging) { console.log(`[SDK] WebSocket closed: ${event.code} - ${event.reason}`); } if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { @@ -73,7 +70,7 @@ export class WebSocketClient { this.ws.onerror = () => { this.isConnecting = false; - if (this.config.enableLogging) { + if (this.enableLogging) { console.error('[SDK] WebSocket error'); } reject(new Error('WebSocket connection failed')); @@ -176,11 +173,11 @@ export class WebSocketClient { const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts); setTimeout(() => { this.reconnectAttempts++; - if (this.config.enableLogging) { + if (this.enableLogging) { console.log(`[SDK] Reconnecting (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); } this.connect().catch(error => { - if (this.config.enableLogging) { + if (this.enableLogging) { console.error('[SDK] Reconnection failed:', error); } });