mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-23 05:02:17 +00:00
feat: Add comprehensive API client modules and JWT authentication to TypeScript SDK
- Add AuthProvider class with API key and JWT token support (create_token, validate_token, refresh_token, get_headers methods) - Add HTTPClient base class with retry logic, error handling, and timeout management - Add SubscriberAPI module (get, list, create, update, suspend, activate, terminate methods) - Add UsageAPI module (get_stats, list_events, get_realtime methods) - Add PaymentAPI module (create_transaction
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { HTTPClient } from './http-client';
|
||||
|
||||
export class GraphQLAPI {
|
||||
constructor(private client: HTTPClient) {}
|
||||
|
||||
async execute(query: string, variables?: Record<string, any>): Promise<any> {
|
||||
const data = { query };
|
||||
if (variables) {
|
||||
(data as any).variables = variables;
|
||||
}
|
||||
|
||||
return this.client.request<any>({
|
||||
method: 'POST',
|
||||
endpoint: '/graphql',
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { AuthProvider } from '../auth';
|
||||
|
||||
export interface RequestConfig {
|
||||
method: string;
|
||||
endpoint: string;
|
||||
data?: any;
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
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<T>(config: RequestConfig): Promise<T> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// Cleanup if needed
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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<string, any>;
|
||||
}): Promise<any> {
|
||||
return this.client.request<any>({
|
||||
method: 'POST',
|
||||
endpoint: '/v1/payments/transactions',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async getTransaction(transactionId: string): Promise<any> {
|
||||
return this.client.request<any>({
|
||||
method: 'GET',
|
||||
endpoint: `/v1/payments/transactions/${transactionId}`,
|
||||
});
|
||||
}
|
||||
|
||||
async listTransactions(
|
||||
subscriberId?: number,
|
||||
status?: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 50
|
||||
): Promise<PaginatedResponse<any>> {
|
||||
const params: Record<string, string> = {
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
};
|
||||
|
||||
if (subscriberId) {
|
||||
params.subscriber_id = subscriberId.toString();
|
||||
}
|
||||
if (status) {
|
||||
params.status = status;
|
||||
}
|
||||
|
||||
return this.client.request<PaginatedResponse<any>>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/payments/transactions',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
async listInvoices(
|
||||
subscriberId?: number,
|
||||
status?: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 50
|
||||
): Promise<PaginatedResponse<Invoice>> {
|
||||
const params: Record<string, string> = {
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
};
|
||||
|
||||
if (subscriberId) {
|
||||
params.subscriber_id = subscriberId.toString();
|
||||
}
|
||||
if (status) {
|
||||
params.status = status;
|
||||
}
|
||||
|
||||
return this.client.request<PaginatedResponse<Invoice>>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/payments/invoices',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
async addPaymentMethod(data: {
|
||||
subscriberId: number;
|
||||
type: string;
|
||||
token: string;
|
||||
isDefault?: boolean;
|
||||
}): Promise<PaymentMethod> {
|
||||
return this.client.request<PaymentMethod>({
|
||||
method: 'POST',
|
||||
endpoint: '/v1/payments/methods',
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { HTTPClient } from './http-client';
|
||||
import { RatingPlan } from '../types';
|
||||
|
||||
export class RatingPlanAPI {
|
||||
constructor(private client: HTTPClient) {}
|
||||
|
||||
async list(): Promise<RatingPlan[]> {
|
||||
return this.client.request<RatingPlan[]>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/rating-plans',
|
||||
});
|
||||
}
|
||||
|
||||
async get(planId: string): Promise<RatingPlan> {
|
||||
return this.client.request<RatingPlan>({
|
||||
method: 'GET',
|
||||
endpoint: `/v1/rating-plans/${planId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<Subscriber> {
|
||||
return this.client.request<Subscriber>({
|
||||
method: 'GET',
|
||||
endpoint: `/v1/subscribers/${subscriberId}`,
|
||||
});
|
||||
}
|
||||
|
||||
async list(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
status?: string
|
||||
): Promise<PaginatedResponse<Subscriber>> {
|
||||
const params: Record<string, string> = {
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
};
|
||||
|
||||
if (status) {
|
||||
params.status = status;
|
||||
}
|
||||
|
||||
return this.client.request<PaginatedResponse<Subscriber>>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/subscribers',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: {
|
||||
imsi: string;
|
||||
msisdn: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
planId: number;
|
||||
organizationId?: string;
|
||||
}): Promise<Subscriber> {
|
||||
return this.client.request<Subscriber>({
|
||||
method: 'POST',
|
||||
endpoint: '/v1/subscribers',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async update(subscriberId: number, data: Record<string, any>): Promise<Subscriber> {
|
||||
return this.client.request<Subscriber>({
|
||||
method: 'PUT',
|
||||
endpoint: `/v1/subscribers/${subscriberId}`,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async suspend(subscriberId: number): Promise<Subscriber> {
|
||||
return this.client.request<Subscriber>({
|
||||
method: 'POST',
|
||||
endpoint: `/v1/subscribers/${subscriberId}/suspend`,
|
||||
});
|
||||
}
|
||||
|
||||
async activate(subscriberId: number): Promise<Subscriber> {
|
||||
return this.client.request<Subscriber>({
|
||||
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}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { HTTPClient } from './http-client';
|
||||
import { SystemStats, HealthStatus } from '../types';
|
||||
|
||||
export class SystemAPI {
|
||||
constructor(private client: HTTPClient) {}
|
||||
|
||||
async getStats(): Promise<SystemStats> {
|
||||
return this.client.request<SystemStats>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/system/stats',
|
||||
});
|
||||
}
|
||||
|
||||
async getHealth(): Promise<HealthStatus> {
|
||||
return this.client.request<HealthStatus>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/health',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<SubscriberAccount> {
|
||||
const params = {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
};
|
||||
|
||||
return this.client.request<SubscriberAccount>({
|
||||
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<PaginatedResponse<UsageEvent>> {
|
||||
const params: Record<string, string> = {
|
||||
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<PaginatedResponse<UsageEvent>>({
|
||||
method: 'GET',
|
||||
endpoint: '/v1/usage/events',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
async getRealtime(subscriberId: number): Promise<RealTimeUsageUpdate> {
|
||||
return this.client.request<RealTimeUsageUpdate>({
|
||||
method: 'GET',
|
||||
endpoint: `/v1/subscribers/${subscriberId}/realtime`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
'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, any> = {}
|
||||
): 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');
|
||||
}
|
||||
}
|
||||
@@ -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, any> = {}): 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<void> {
|
||||
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<SystemStats> {
|
||||
return this.http.get<SystemStats>('/api/system/stats');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system health status
|
||||
*/
|
||||
async getHealthStatus(): Promise<HealthStatus> {
|
||||
return this.http.get<HealthStatus>('/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';
|
||||
|
||||
@@ -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<string, WebSocketEventHandler[]> = new Map();
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 5;
|
||||
private reconnectDelay = 1000;
|
||||
private isConnecting = false;
|
||||
private pingInterval: ReturnType<typeof setInterval> | 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<void>((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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user