- Added new SimFeaturesUpdateRequest interface to handle optional SIM feature updates. - Implemented updateSimFeatures method in SimManagementService to process feature updates including voicemail, call waiting, international roaming, and network type. - Expanded SubscriptionsController with a new endpoint for updating SIM features. - Introduced SimFeatureToggles component for managing service options in the UI. - Enhanced DataUsageChart and SimDetailsCard components to support embedded rendering and improved styling. - Updated layout and design for better user experience in the SIM management section.
663 lines
20 KiB
TypeScript
663 lines
20 KiB
TypeScript
import { Injectable, Inject, BadRequestException, InternalServerErrorException } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { Logger } from 'nestjs-pino';
|
|
import {
|
|
FreebititConfig,
|
|
FreebititAuthRequest,
|
|
FreebititAuthResponse,
|
|
FreebititAccountDetailsRequest,
|
|
FreebititAccountDetailsResponse,
|
|
FreebititTrafficInfoRequest,
|
|
FreebititTrafficInfoResponse,
|
|
FreebititTopUpRequest,
|
|
FreebititTopUpResponse,
|
|
FreebititQuotaHistoryRequest,
|
|
FreebititQuotaHistoryResponse,
|
|
FreebititPlanChangeRequest,
|
|
FreebititPlanChangeResponse,
|
|
FreebititCancelPlanRequest,
|
|
FreebititCancelPlanResponse,
|
|
FreebititEsimReissueRequest,
|
|
FreebititEsimReissueResponse,
|
|
FreebititEsimAddAccountRequest,
|
|
FreebititEsimAddAccountResponse,
|
|
SimDetails,
|
|
SimUsage,
|
|
SimTopUpHistory,
|
|
FreebititError,
|
|
FreebititAddSpecRequest,
|
|
FreebititAddSpecResponse
|
|
} from './interfaces/freebit.types';
|
|
|
|
@Injectable()
|
|
export class FreebititService {
|
|
private readonly config: FreebititConfig;
|
|
private authKeyCache: {
|
|
token: string;
|
|
expiresAt: number;
|
|
} | null = null;
|
|
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
@Inject(Logger) private readonly logger: Logger,
|
|
) {
|
|
this.config = {
|
|
baseUrl: this.configService.get<string>('FREEBIT_BASE_URL') || 'https://i1.mvno.net/emptool/api',
|
|
oemId: this.configService.get<string>('FREEBIT_OEM_ID') || 'PASI',
|
|
oemKey: this.configService.get<string>('FREEBIT_OEM_KEY') || '',
|
|
timeout: this.configService.get<number>('FREEBIT_TIMEOUT') || 30000,
|
|
retryAttempts: this.configService.get<number>('FREEBIT_RETRY_ATTEMPTS') || 3,
|
|
detailsEndpoint: this.configService.get<string>('FREEBIT_DETAILS_ENDPOINT') || '/master/getAcnt/',
|
|
};
|
|
|
|
// Warn if critical configuration is missing
|
|
if (!this.config.oemKey) {
|
|
this.logger.warn('FREEBIT_OEM_KEY is not configured. SIM management features will not work.');
|
|
}
|
|
|
|
this.logger.debug('Freebit service initialized', {
|
|
baseUrl: this.config.baseUrl,
|
|
oemId: this.config.oemId,
|
|
hasOemKey: !!this.config.oemKey,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Map Freebit SIM status to portal status
|
|
*/
|
|
private mapSimStatus(freebititStatus: string): 'active' | 'suspended' | 'cancelled' | 'pending' {
|
|
switch (freebititStatus) {
|
|
case 'active':
|
|
return 'active';
|
|
case 'suspended':
|
|
return 'suspended';
|
|
case 'temporary':
|
|
case 'waiting':
|
|
return 'pending';
|
|
case 'obsolete':
|
|
return 'cancelled';
|
|
default:
|
|
return 'pending';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get or refresh authentication token
|
|
*/
|
|
private async getAuthKey(): Promise<string> {
|
|
// Check if we have a valid cached token
|
|
if (this.authKeyCache && this.authKeyCache.expiresAt > Date.now()) {
|
|
return this.authKeyCache.token;
|
|
}
|
|
|
|
try {
|
|
// Check if configuration is available
|
|
if (!this.config.oemKey) {
|
|
throw new Error('Freebit API not configured: FREEBIT_OEM_KEY is missing');
|
|
}
|
|
|
|
const request: FreebititAuthRequest = {
|
|
oemId: this.config.oemId,
|
|
oemKey: this.config.oemKey,
|
|
};
|
|
|
|
const response = await fetch(`${this.config.baseUrl}/authOem/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: `json=${JSON.stringify(request)}`,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json() as FreebititAuthResponse;
|
|
|
|
if (data.resultCode !== '100') {
|
|
throw new FreebititErrorImpl(
|
|
`Authentication failed: ${data.status.message}`,
|
|
data.resultCode,
|
|
data.status.statusCode,
|
|
data.status.message
|
|
);
|
|
}
|
|
|
|
// Cache the token for 50 minutes (assuming 60min expiry)
|
|
this.authKeyCache = {
|
|
token: data.authKey,
|
|
expiresAt: Date.now() + 50 * 60 * 1000,
|
|
};
|
|
|
|
this.logger.log('Successfully authenticated with Freebit API');
|
|
return data.authKey;
|
|
} catch (error: any) {
|
|
this.logger.error('Failed to authenticate with Freebit API', { error: error.message });
|
|
throw new InternalServerErrorException('Failed to authenticate with Freebit API');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make authenticated API request with error handling
|
|
*/
|
|
private async makeAuthenticatedRequest<T>(
|
|
endpoint: string,
|
|
data: any
|
|
): Promise<T> {
|
|
const authKey = await this.getAuthKey();
|
|
const requestData = { ...data, authKey };
|
|
|
|
try {
|
|
const url = `${this.config.baseUrl}${endpoint}`;
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: `json=${JSON.stringify(requestData)}`,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
let bodySnippet: string | undefined;
|
|
try {
|
|
const text = await response.text();
|
|
bodySnippet = text ? text.slice(0, 500) : undefined;
|
|
} catch {}
|
|
this.logger.error('Freebit API non-OK response', {
|
|
endpoint,
|
|
url,
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
body: bodySnippet,
|
|
});
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const responseData = await response.json() as T;
|
|
|
|
// Check for API-level errors
|
|
if (responseData && (responseData as any).resultCode !== '100') {
|
|
const errorData = responseData as any;
|
|
throw new FreebititErrorImpl(
|
|
`API Error: ${errorData.status?.message || 'Unknown error'}`,
|
|
errorData.resultCode,
|
|
errorData.status?.statusCode,
|
|
errorData.status?.message
|
|
);
|
|
}
|
|
|
|
this.logger.debug('Freebit API Request Success', {
|
|
endpoint,
|
|
resultCode: (responseData as any).resultCode,
|
|
});
|
|
|
|
return responseData;
|
|
} catch (error) {
|
|
if (error instanceof FreebititErrorImpl) {
|
|
throw error;
|
|
}
|
|
|
|
this.logger.error(`Freebit API request failed: ${endpoint}`, { error: (error as any).message });
|
|
throw new InternalServerErrorException(`Freebit API request failed: ${(error as any).message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get detailed SIM account information
|
|
*/
|
|
async getSimDetails(account: string): Promise<SimDetails> {
|
|
try {
|
|
const request: Omit<FreebititAccountDetailsRequest, 'authKey'> = {
|
|
version: '2',
|
|
requestDatas: [{ kind: 'MVNO', account }],
|
|
};
|
|
|
|
const configured = this.config.detailsEndpoint || '/master/getAcnt/';
|
|
const candidates = Array.from(new Set([
|
|
configured,
|
|
configured.replace(/\/$/, ''),
|
|
'/master/getAcnt/',
|
|
'/master/getAcnt',
|
|
'/mvno/getAccountDetail/',
|
|
'/mvno/getAccountDetail',
|
|
'/mvno/getAcntDetail/',
|
|
'/mvno/getAcntDetail',
|
|
'/mvno/getAccountInfo/',
|
|
'/mvno/getAccountInfo',
|
|
'/mvno/getSubscriberInfo/',
|
|
'/mvno/getSubscriberInfo',
|
|
'/mvno/getInfo/',
|
|
'/mvno/getInfo',
|
|
'/master/getDetail/',
|
|
'/master/getDetail',
|
|
]));
|
|
|
|
let response: FreebititAccountDetailsResponse | undefined;
|
|
let lastError: any;
|
|
for (const ep of candidates) {
|
|
try {
|
|
if (ep !== candidates[0]) {
|
|
this.logger.warn(`Retrying Freebit account details with alternative endpoint: ${ep}`);
|
|
}
|
|
response = await this.makeAuthenticatedRequest<FreebititAccountDetailsResponse>(ep, request);
|
|
break; // success
|
|
} catch (err: any) {
|
|
lastError = err;
|
|
if (typeof err?.message === 'string' && err.message.includes('HTTP 404')) {
|
|
// try next candidate
|
|
continue;
|
|
}
|
|
// non-404 error, rethrow
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
if (!response) {
|
|
throw lastError || new InternalServerErrorException('Failed to fetch SIM details: all endpoints failed');
|
|
}
|
|
|
|
const datas = (response as any).responseDatas;
|
|
const list = Array.isArray(datas) ? datas : (datas ? [datas] : []);
|
|
if (!list.length) {
|
|
throw new BadRequestException('No SIM details found for this account');
|
|
}
|
|
// Prefer the MVNO entry if present
|
|
const mvno = list.find((d: any) => (d.kind || '').toString().toUpperCase() === 'MVNO') || list[0];
|
|
const simData = mvno as any;
|
|
|
|
const startDateRaw = simData.startDate ? String(simData.startDate) : undefined;
|
|
const startDate = startDateRaw && /^\d{8}$/.test(startDateRaw)
|
|
? `${startDateRaw.slice(0,4)}-${startDateRaw.slice(4,6)}-${startDateRaw.slice(6,8)}`
|
|
: startDateRaw;
|
|
|
|
const simDetails: SimDetails = {
|
|
account: String(simData.account ?? account),
|
|
msisdn: String(simData.account ?? account),
|
|
iccid: simData.iccid ? String(simData.iccid) : undefined,
|
|
imsi: simData.imsi ? String(simData.imsi) : undefined,
|
|
eid: simData.eid,
|
|
planCode: simData.planCode,
|
|
status: this.mapSimStatus(String(simData.state || 'pending')),
|
|
simType: simData.eid ? 'esim' : 'physical',
|
|
size: simData.size,
|
|
hasVoice: simData.talk === 10,
|
|
hasSms: simData.sms === 10,
|
|
remainingQuotaKb: typeof simData.quota === 'number' ? simData.quota : 0,
|
|
remainingQuotaMb: typeof simData.quota === 'number' ? Math.round((simData.quota / 1024) * 100) / 100 : 0,
|
|
startDate,
|
|
ipv4: simData.ipv4,
|
|
ipv6: simData.ipv6,
|
|
voiceMailEnabled: simData.voicemail === 10 || simData.voiceMail === 10,
|
|
callWaitingEnabled: simData.callwaiting === 10 || simData.callWaiting === 10,
|
|
internationalRoamingEnabled: simData.worldwing === 10 || simData.worldWing === 10,
|
|
networkType: simData.contractLine || undefined,
|
|
pendingOperations: simData.async ? [{
|
|
operation: simData.async.func,
|
|
scheduledDate: String(simData.async.date),
|
|
}] : undefined,
|
|
};
|
|
|
|
this.logger.log(`Retrieved SIM details for account ${account}`, {
|
|
account,
|
|
status: simDetails.status,
|
|
planCode: simDetails.planCode,
|
|
});
|
|
|
|
return simDetails;
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to get SIM details for account ${account}`, { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get SIM data usage information
|
|
*/
|
|
async getSimUsage(account: string): Promise<SimUsage> {
|
|
try {
|
|
const request: Omit<FreebititTrafficInfoRequest, 'authKey'> = { account };
|
|
|
|
const response = await this.makeAuthenticatedRequest<FreebititTrafficInfoResponse>(
|
|
'/mvno/getTrafficInfo/',
|
|
request
|
|
);
|
|
|
|
const todayUsageKb = parseInt(response.traffic.today, 10) || 0;
|
|
const recentDaysData = response.traffic.inRecentDays.split(',').map((usage, index) => ({
|
|
date: new Date(Date.now() - (index + 1) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
|
usageKb: parseInt(usage, 10) || 0,
|
|
usageMb: Math.round(parseInt(usage, 10) / 1024 * 100) / 100,
|
|
}));
|
|
|
|
const simUsage: SimUsage = {
|
|
account,
|
|
todayUsageKb,
|
|
todayUsageMb: Math.round(todayUsageKb / 1024 * 100) / 100,
|
|
recentDaysUsage: recentDaysData,
|
|
isBlacklisted: response.traffic.blackList === '10',
|
|
};
|
|
|
|
this.logger.log(`Retrieved SIM usage for account ${account}`, {
|
|
account,
|
|
todayUsageMb: simUsage.todayUsageMb,
|
|
isBlacklisted: simUsage.isBlacklisted,
|
|
});
|
|
|
|
return simUsage;
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to get SIM usage for account ${account}`, { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Top up SIM data quota
|
|
*/
|
|
async topUpSim(account: string, quotaMb: number, options: {
|
|
campaignCode?: string;
|
|
expiryDate?: string;
|
|
scheduledAt?: string;
|
|
} = {}): Promise<void> {
|
|
try {
|
|
const quotaKb = quotaMb * 1024;
|
|
|
|
const request: Omit<FreebititTopUpRequest, 'authKey'> = {
|
|
account,
|
|
quota: quotaKb,
|
|
quotaCode: options.campaignCode,
|
|
expire: options.expiryDate,
|
|
};
|
|
|
|
// Use PA05-22 for scheduled top-ups, PA04-04 for immediate
|
|
const endpoint = options.scheduledAt ? '/mvno/eachQuota/' : '/master/addSpec/';
|
|
|
|
if (options.scheduledAt && endpoint === '/mvno/eachQuota/') {
|
|
(request as any).runTime = options.scheduledAt;
|
|
}
|
|
|
|
await this.makeAuthenticatedRequest<FreebititTopUpResponse>(endpoint, request);
|
|
|
|
this.logger.log(`Successfully topped up SIM ${account}`, {
|
|
account,
|
|
quotaMb,
|
|
quotaKb,
|
|
campaignCode: options.campaignCode,
|
|
scheduled: !!options.scheduledAt,
|
|
});
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to top up SIM ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
quotaMb,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get SIM top-up history
|
|
*/
|
|
async getSimTopUpHistory(account: string, fromDate: string, toDate: string): Promise<SimTopUpHistory> {
|
|
try {
|
|
const request: Omit<FreebititQuotaHistoryRequest, 'authKey'> = {
|
|
account,
|
|
fromDate,
|
|
toDate,
|
|
};
|
|
|
|
const response = await this.makeAuthenticatedRequest<FreebititQuotaHistoryResponse>(
|
|
'/mvno/getQuotaHistory/',
|
|
request
|
|
);
|
|
|
|
const history: SimTopUpHistory = {
|
|
account,
|
|
totalAdditions: response.total,
|
|
additionCount: response.count,
|
|
history: response.quotaHistory.map(item => ({
|
|
quotaKb: parseInt(item.quota, 10),
|
|
quotaMb: Math.round(parseInt(item.quota, 10) / 1024 * 100) / 100,
|
|
addedDate: item.date,
|
|
expiryDate: item.expire,
|
|
campaignCode: item.quotaCode,
|
|
})),
|
|
};
|
|
|
|
this.logger.log(`Retrieved SIM top-up history for account ${account}`, {
|
|
account,
|
|
totalAdditions: history.totalAdditions,
|
|
additionCount: history.additionCount,
|
|
});
|
|
|
|
return history;
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to get SIM top-up history for account ${account}`, { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change SIM plan
|
|
*/
|
|
async changeSimPlan(account: string, newPlanCode: string, options: {
|
|
assignGlobalIp?: boolean;
|
|
scheduledAt?: string;
|
|
} = {}): Promise<{ ipv4?: string; ipv6?: string }> {
|
|
try {
|
|
const request: Omit<FreebititPlanChangeRequest, 'authKey'> = {
|
|
account,
|
|
plancode: newPlanCode,
|
|
globalip: options.assignGlobalIp ? '1' : '0',
|
|
runTime: options.scheduledAt,
|
|
};
|
|
|
|
const response = await this.makeAuthenticatedRequest<FreebititPlanChangeResponse>(
|
|
'/mvno/changePlan/',
|
|
request
|
|
);
|
|
|
|
this.logger.log(`Successfully changed SIM plan for account ${account}`, {
|
|
account,
|
|
newPlanCode,
|
|
assignGlobalIp: options.assignGlobalIp,
|
|
scheduled: !!options.scheduledAt,
|
|
});
|
|
|
|
return {
|
|
ipv4: response.ipv4,
|
|
ipv6: response.ipv6,
|
|
};
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to change SIM plan for account ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
newPlanCode,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update SIM optional features (voicemail, call waiting, international roaming, network type)
|
|
* Uses AddSpec endpoint for immediate changes
|
|
*/
|
|
async updateSimFeatures(account: string, features: {
|
|
voiceMailEnabled?: boolean;
|
|
callWaitingEnabled?: boolean;
|
|
internationalRoamingEnabled?: boolean;
|
|
networkType?: string; // '4G' | '5G'
|
|
}): Promise<void> {
|
|
try {
|
|
const request: Omit<FreebititAddSpecRequest, 'authKey'> = {
|
|
account,
|
|
};
|
|
|
|
if (typeof features.voiceMailEnabled === 'boolean') {
|
|
request.voiceMail = features.voiceMailEnabled ? '10' as const : '20' as const;
|
|
request.voicemail = request.voiceMail; // include alternate casing for compatibility
|
|
}
|
|
if (typeof features.callWaitingEnabled === 'boolean') {
|
|
request.callWaiting = features.callWaitingEnabled ? '10' as const : '20' as const;
|
|
request.callwaiting = request.callWaiting;
|
|
}
|
|
if (typeof features.internationalRoamingEnabled === 'boolean') {
|
|
request.worldWing = features.internationalRoamingEnabled ? '10' as const : '20' as const;
|
|
request.worldwing = request.worldWing;
|
|
}
|
|
if (features.networkType) {
|
|
request.contractLine = features.networkType;
|
|
}
|
|
|
|
await this.makeAuthenticatedRequest<FreebititAddSpecResponse>('/master/addSpec/', request);
|
|
|
|
this.logger.log(`Updated SIM features for account ${account}`, {
|
|
account,
|
|
voiceMailEnabled: features.voiceMailEnabled,
|
|
callWaitingEnabled: features.callWaitingEnabled,
|
|
internationalRoamingEnabled: features.internationalRoamingEnabled,
|
|
networkType: features.networkType,
|
|
});
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to update SIM features for account ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel SIM service
|
|
*/
|
|
async cancelSim(account: string, scheduledAt?: string): Promise<void> {
|
|
try {
|
|
const request: Omit<FreebititCancelPlanRequest, 'authKey'> = {
|
|
account,
|
|
runTime: scheduledAt,
|
|
};
|
|
|
|
await this.makeAuthenticatedRequest<FreebititCancelPlanResponse>(
|
|
'/mvno/releasePlan/',
|
|
request
|
|
);
|
|
|
|
this.logger.log(`Successfully cancelled SIM for account ${account}`, {
|
|
account,
|
|
scheduled: !!scheduledAt,
|
|
});
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to cancel SIM for account ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reissue eSIM profile using reissueProfile endpoint
|
|
*/
|
|
async reissueEsimProfile(account: string): Promise<void> {
|
|
try {
|
|
const request: Omit<FreebititEsimReissueRequest, 'authKey'> = { account };
|
|
|
|
await this.makeAuthenticatedRequest<FreebititEsimReissueResponse>(
|
|
'/esim/reissueProfile/',
|
|
request
|
|
);
|
|
|
|
this.logger.log(`Successfully reissued eSIM profile for account ${account}`, { account });
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to reissue eSIM profile for account ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reissue eSIM profile using addAcnt endpoint (enhanced method based on Salesforce implementation)
|
|
*/
|
|
async reissueEsimProfileEnhanced(
|
|
account: string,
|
|
newEid: string,
|
|
options: {
|
|
oldProductNumber?: string;
|
|
oldEid?: string;
|
|
planCode?: string;
|
|
} = {}
|
|
): Promise<void> {
|
|
try {
|
|
const request: Omit<FreebititEsimAddAccountRequest, 'authKey'> = {
|
|
aladinOperated: '20',
|
|
account,
|
|
eid: newEid,
|
|
addKind: 'R', // R = reissue
|
|
reissue: {
|
|
oldProductNumber: options.oldProductNumber,
|
|
oldEid: options.oldEid,
|
|
},
|
|
};
|
|
|
|
// Add optional fields
|
|
if (options.planCode) {
|
|
request.planCode = options.planCode;
|
|
}
|
|
|
|
await this.makeAuthenticatedRequest<FreebititEsimAddAccountResponse>(
|
|
'/mvno/esim/addAcnt/',
|
|
request
|
|
);
|
|
|
|
this.logger.log(`Successfully reissued eSIM profile via addAcnt for account ${account}`, {
|
|
account,
|
|
newEid,
|
|
oldProductNumber: options.oldProductNumber,
|
|
oldEid: options.oldEid,
|
|
});
|
|
} catch (error: any) {
|
|
this.logger.error(`Failed to reissue eSIM profile via addAcnt for account ${account}`, {
|
|
error: error.message,
|
|
account,
|
|
newEid,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Health check for Freebit API
|
|
*/
|
|
async healthCheck(): Promise<boolean> {
|
|
try {
|
|
await this.getAuthKey();
|
|
return true;
|
|
} catch (error: any) {
|
|
this.logger.error('Freebit API health check failed', { error: error.message });
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom error class for Freebit API errors
|
|
class FreebititErrorImpl extends Error {
|
|
public readonly resultCode: string;
|
|
public readonly statusCode: string;
|
|
public readonly freebititMessage: string;
|
|
|
|
constructor(
|
|
message: string,
|
|
resultCode: string,
|
|
statusCode: string,
|
|
freebititMessage: string
|
|
) {
|
|
super(message);
|
|
this.name = 'FreebititError';
|
|
this.resultCode = resultCode;
|
|
this.statusCode = statusCode;
|
|
this.freebititMessage = freebititMessage;
|
|
}
|
|
}
|