Skip to main content
Glama
api-client.js4.63 kB
import axios from 'axios'; import pRetry from 'p-retry'; import pThrottle from 'p-throttle'; import { config, logger } from './config.js'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); const { encryptPassword: pidCryptEncrypt } = require('./encryption.cjs'); export class IGApiError extends Error { constructor(message, status, code) { super(message); this.name = 'IGApiError'; this.status = status; this.code = code; } } export class IGApiClient { constructor() { this.axiosInstance = null; this.throttle = pThrottle({ limit: 60, interval: 60000, strict: true }); this.initializeAxios(); } initializeAxios() { // Delay baseURL setting until first request when config is ready this.axiosInstance = axios.create({ timeout: 30000, headers: { 'Accept': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8' } }); this.axiosInstance.interceptors.request.use( (requestConfig) => { // Set baseURL dynamically when config is ready if (!requestConfig.baseURL) { requestConfig.baseURL = config.getApiUrl() + '/gateway/deal'; } if (config.credentials?.apiKey) { requestConfig.headers['X-IG-API-KEY'] = config.credentials.apiKey; } try { const tokens = config.getSessionTokens(); if (tokens) { requestConfig.headers['X-Security-Token'] = tokens.xSecurityToken; requestConfig.headers['CST'] = tokens.cst; } } catch (error) { // No session tokens yet, likely a login request } logger.debug(`API Request: ${requestConfig.method?.toUpperCase()} ${requestConfig.url}`); return requestConfig; }, (error) => Promise.reject(error) ); this.axiosInstance.interceptors.response.use( (response) => { logger.debug(`API Response: ${response.status} ${response.config.url}`); return response; }, (error) => { if (error.response) { logger.error(`API Error: ${error.response.status} ${error.response.data?.errorCode || ''}`); throw new IGApiError( error.response.data?.errorCode || error.message, error.response.status, error.response.data?.errorCode ); } throw error; } ); } async request(method, path, data = null, headers = {}, version = 1) { const requestConfig = { method, url: path, headers: { ...headers, Version: version } }; if (data) { if (method === 'GET') { requestConfig.params = data; } else { requestConfig.data = data; } } const throttledRequest = this.throttle(async () => { return await pRetry( async () => { const response = await this.axiosInstance(requestConfig); return { status: response.status, headers: response.headers, data: response.data }; }, { retries: 3, onFailedAttempt: (error) => { logger.warn(`Request failed, attempt ${error.attemptNumber}: ${error.message}`); if (error.attemptNumber === 3) { logger.error(`Request failed after ${error.attemptNumber} attempts`); } }, shouldRetry: (error) => { if (error.status === 401) return false; if (error.status >= 500) return true; if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') return true; return false; } } ); }); return await throttledRequest(); } async get(path, version = 1) { return this.request('GET', path, null, {}, version); } async post(path, data, version = 1) { return this.request('POST', path, data, {}, version); } async put(path, data, version = 1) { return this.request('PUT', path, data, {}, version); } async delete(path, data = null, version = 1) { const headers = { '_method': 'DELETE' }; return this.request('POST', path, data, headers, version); } encryptPassword(password, encryptionKey, timestamp) { try { // Use pidCrypt for IG-compatible RSA encryption return pidCryptEncrypt(password, encryptionKey, timestamp); } catch (error) { logger.error('Password encryption failed:', error); throw new Error('Failed to encrypt password'); } } } export const apiClient = new IGApiClient();

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/kea0811/ig-trading-mcp'

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