Skip to main content
Glama
cmp_client.ts10.7 kB
export enum SIMState { PRE_ACTIVATION = 2, TEST = 3, SILENT = 4, STANDBY = 5, ACTIVE = 6, SHUTDOWN = 7, PAUSE = 8, PRE_LOGOUT = 10, LOGOUT = 11, } export function getStateName(stateCode: number): string { const stateMap: Record<number, string> = { 2: "Pre-activation", 3: "Test", 4: "Silent", 5: "Standby", 6: "Active", 7: "Shutdown", 8: "Pause", 10: "Pre-logout", 11: "Logout", }; return stateMap[stateCode] || `Unknown status (${stateCode})`; } export interface SIMListQuery { pageNum?: number; pageSize?: number; enterpriseDataPlan?: string; expirationTimeStart?: string; expirationTimeEnd?: string; iccidStart?: string; iccidEnd?: string; label?: string; simState?: number; simType?: string; } export interface APIResponse<T = any> { code: number; msg?: string; reqId?: string; data: T; } export interface SIMUsageQuery { iccid: string; month: string; // yyyyMM format } export interface DataUsageDetail { dataAllowance: string; // Total data allowance in MB dataUsage: string; // Used data in MB orderName: string; // Order name outsideRegionDataUsage: string; // Outside region data usage in MB type: number; // 1: Activation period plan, 2: Test period plan, 3: Data package } export interface SIMUsageResponse { dataUsageDetails: DataUsageDetail[]; iccid: string; month: string; // yyyyMM format outsideRegionDataUsage: string; // Outside region data usage in MB remainingData: string; // Remaining data in MB totalDataAllowance: string; // Total data allowance in MB totalDataUsage: string; // Total used data in MB } export interface EuiccPageQuery { childEnterpriseId?: number; // Child enterprise ID to query iccid?: string; // ICCID filter pageNum?: number; // Page number, default 1 pageSize?: number; // Records per page, default 10, max 1000 profileStatus?: number; // Profile status filter } export interface EuiccPageDto { eid: string; // eID enterpriseName: string; // Enterprise name iccid: string; // ICCID imei: string; // IMEI lastOperateTime: string; // Last operation time profileNum: number; // Profile number profileStatus: number; // Profile status: 1-9 (see enum below) profileType: string; // Profile type: 0=Test, 1=Provisioning, 2=Operational } export interface EuiccPageData { current: number; // Current page number extend: EuiccPageDto; // Extended field (seems to be sample data) list: EuiccPageDto[]; // Data list pages: number; // Total pages size: number; // Page size total: number; // Total records } export interface EuiccPageResponse { code: number; data: EuiccPageData; msg: string; reqId: string; } export enum ProfileStatus { NOT_DOWNLOADED = 1, DOWNLOADING = 2, DOWNLOADED = 3, ENABLING = 4, ENABLED = 5, DISABLING = 6, DISABLED = 7, DELETING = 8, DELETED = 9, } export function getProfileStatusName(status: number): string { const statusMap: Record<number, string> = { 1: "Not Downloaded", 2: "Downloading", 3: "Downloaded", 4: "Enabling", 5: "Enabled", 6: "Disabling", 7: "Disabled", 8: "Deleting", 9: "Deleted", }; return statusMap[status] || `Unknown Status (${status})`; } export function getProfileTypeName(type: string): string { const typeMap: Record<string, string> = { "0": "Test Profile", "1": "Provisioning Profile", "2": "Operational Profile", }; return typeMap[type] || `Unknown Type (${type})`; } export class CMPClient { private appKey: string; private appSecret: string; private endpoint: string; constructor(appKey: string, appSecret: string, endpoint: string) { this.appKey = appKey; this.appSecret = appSecret; this.endpoint = endpoint.replace(/\/$/, ""); } private async generateSignature(timestamp: number, requestBody = ""): Promise<string> { const signContent = this.appKey + timestamp.toString() + requestBody; const encoder = new TextEncoder(); const keyData = encoder.encode(this.appSecret); const messageData = encoder.encode(signContent); const cryptoKey = await crypto.subtle.importKey( "raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData); return Array.from(new Uint8Array(signature)) .map(b => b.toString(16).padStart(2, "0")) .join(""); } private async getHeaders(requestBody = ""): Promise<Record<string, string>> { const timestamp = Math.floor(Date.now() / 1000); const signature = await this.generateSignature(timestamp, requestBody); return { "Content-Type": "application/json", "APP-Key": this.appKey, "Signature": signature, "Timestamp": timestamp.toString(), }; } private async makeRequest<T = any>( method: string, resourcePath: string, data?: any, params?: Record<string, any> ): Promise<APIResponse<T>> { // Fix URL construction - ensure resourcePath is appended correctly const baseUrl = this.endpoint.endsWith('/') ? this.endpoint.slice(0, -1) : this.endpoint; const path = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath; const url = new URL(`${baseUrl}/${path}`); if (params) { for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null) { url.searchParams.append(key, String(value)); } } } const requestBody = data ? JSON.stringify(data) : ""; const headers = await this.getHeaders(requestBody); // Debug logging console.log(`🔍 Making ${method} request to: ${url.toString()}`); console.log(`📊 Request body: ${requestBody}`); console.log(`🔑 Headers: ${JSON.stringify(headers)}`); try { const response = await fetch(url.toString(), { method, headers, body: requestBody || undefined, }); console.log(`📡 Response status: ${response.status} ${response.statusText}`); if (!response.ok) { const responseText = await response.text(); console.log(`❌ Response body: ${responseText}`); throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); } const result: APIResponse<T> = await response.json(); console.log(`📋 Response data: ${JSON.stringify(result)}`); // Handle different response formats if (result.code === undefined) { // Some APIs might return data directly without wrapper if (typeof result === 'object' && result !== null) { console.log(`⚠️ API returned data without standard wrapper, treating as success`); return { code: 200, msg: "OK", data: result as T } as APIResponse<T>; } else { throw new Error(`API Error: Invalid response format - ${JSON.stringify(result)}`); } } if (result.code !== 200) { throw new Error(`API Error [${result.code}]: ${result.msg || "Unknown error"}`); } return result; } catch (error) { if (error instanceof Error) { throw new Error(`Request failed: ${error.message}`); } throw new Error("Request failed: Unknown error"); } } async get<T = any>(resourcePath: string, params?: Record<string, any>): Promise<APIResponse<T>> { return this.makeRequest<T>("GET", resourcePath, undefined, params); } async post<T = any>(resourcePath: string, data?: any): Promise<APIResponse<T>> { return this.makeRequest<T>("POST", resourcePath, data); } async put<T = any>(resourcePath: string, data?: any): Promise<APIResponse<T>> { return this.makeRequest<T>("PUT", resourcePath, data); } async delete<T = any>(resourcePath: string, data?: any): Promise<APIResponse<T>> { return this.makeRequest<T>("DELETE", resourcePath, data); } async querySimList(options: SIMListQuery = {}): Promise<APIResponse> { const { pageNum = 1, pageSize = 10, enterpriseDataPlan, expirationTimeStart, expirationTimeEnd, iccidStart, iccidEnd, label, simState, simType, } = options; const data: Record<string, any> = { pageNum, pageSize: Math.min(pageSize, 1000), }; if (enterpriseDataPlan) data.enterpriseDataPlan = enterpriseDataPlan; if (expirationTimeStart) data.expirationTimeStart = expirationTimeStart; if (expirationTimeEnd) data.expirationTimeEnd = expirationTimeEnd; if (iccidStart) data.iccidStart = iccidStart; if (iccidEnd) data.iccidEnd = iccidEnd; if (label) data.label = label; if (simState !== undefined) data.simState = simState; if (simType) data.simType = simType; return this.post("/sim/page", data); } async querySimDetail(iccid: string): Promise<APIResponse> { if (!iccid || !iccid.trim()) { throw new Error("ICCID cannot be empty"); } return this.post("/sim/detail", { iccid: iccid.trim() }); } async querySimMonthData(options: SIMUsageQuery): Promise<APIResponse<SIMUsageResponse>> { const { iccid, month } = options; if (!iccid || !iccid.trim()) { throw new Error("ICCID cannot be empty"); } if (!month || !month.trim()) { throw new Error("Month cannot be empty"); } // Validate month format (yyyyMM) if (!/^\d{6}$/.test(month.trim())) { throw new Error("Month format must be yyyyMM (e.g., 202301)"); } return this.post("/sim/queryMonthData", { iccid: iccid.trim(), month: month.trim() }); } async queryEuiccPage(options: EuiccPageQuery = {}): Promise<APIResponse<EuiccPageData>> { const { childEnterpriseId, iccid, pageNum = 1, pageSize = 10, profileStatus, } = options; const data: Record<string, any> = { pageNum, pageSize: Math.min(pageSize, 1000), }; // Add optional filters if (childEnterpriseId !== undefined) data.childEnterpriseId = childEnterpriseId; if (iccid && iccid.trim()) data.iccid = iccid.trim(); if (profileStatus !== undefined) data.profileStatus = profileStatus; // Validate profileStatus if provided if (profileStatus !== undefined && (profileStatus < 1 || profileStatus > 9)) { throw new Error("Profile status must be between 1-9 (see ProfileStatus enum)"); } return this.post("/esim/euicc/page", data); } formatDataUsage(bytesValue: number): string { if (bytesValue === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = bytesValue; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } if (unitIndex === 0) { return `${Math.round(size)} ${units[unitIndex]}`; } return `${size.toFixed(2)} ${units[unitIndex]}`; } // Helper functions for cursor-based pagination (using Web APIs for Cloudflare Workers) static createCursor(data: any): string { return btoa(JSON.stringify(data)); } static parseCursor(cursor: string): any { try { return JSON.parse(atob(cursor)); } catch { throw new Error("Invalid cursor format"); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Acceleronix/cmp-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server