UNPKG

@copytrade/unified-broker

Version:

Unified broker interface library for Indian stock market brokers with plugin architecture

371 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FyersService = void 0; const { fyersModel } = require('fyers-api-v3'); class FyersService { constructor() { this.accessToken = null; this.refreshToken = null; this.appId = ''; this.clientId = ''; this.secretKey = ''; // Initialize the official Fyers API client this.fyers = new fyersModel({ path: process.cwd() + '/logs', enableLogging: true }); } // Generate auth URL for user to visit generateAuthUrl(credentials) { this.appId = credentials.clientId; this.fyers.setAppId(credentials.clientId); this.fyers.setRedirectUrl(credentials.redirectUri); const authUrl = this.fyers.generateAuthCode(); console.log('🔗 Auth URL generated:', authUrl); return authUrl; } // Generate access token from auth code async generateAccessToken(authCode, credentials) { try { // Store credentials for refresh token usage this.clientId = credentials.clientId; this.secretKey = credentials.secretKey; // Set App ID before generating access token this.fyers.setAppId(credentials.clientId); const response = await this.fyers.generate_access_token({ client_id: credentials.clientId, secret_key: credentials.secretKey, auth_code: authCode }); if (response.s === 'ok') { this.accessToken = response.access_token; this.refreshToken = response.refresh_token; // Capture refresh token this.fyers.setAccessToken(response.access_token); console.log('✅ Fyers access token generated successfully'); console.log('✅ Fyers refresh token captured:', !!response.refresh_token); // Get user profile to extract actual account ID try { const profileResponse = await this.fyers.get_profile(); if (profileResponse.s === 'ok' && profileResponse.data) { const accountId = profileResponse.data.fy_id || profileResponse.data.id || profileResponse.data.user_id; console.log(`✅ Fyers profile fetched, account ID: ${accountId}`); // Calculate expiry time (Fyers tokens typically expire in 24 hours) const expiryTime = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); return { success: true, accessToken: response.access_token, refreshToken: response.refresh_token, accountId: accountId, // Return actual Fyers account ID message: 'Access token generated successfully', expiryTime: expiryTime, }; } else { console.log('⚠️ Profile fetch failed, using client ID as fallback'); } } catch (profileError) { console.log('⚠️ Profile fetch error:', profileError.message); } // Fallback: return without account ID return { success: true, accessToken: response.access_token, message: 'Access token generated successfully', }; } else { throw new Error(response.message || 'Failed to generate access token'); } } catch (error) { console.error('🚨 Failed to generate access token:', error); return { success: false, message: error.message || 'Access token generation failed', }; } } // Complete login flow - returns auth URL for user to visit async login(credentials) { try { const authUrl = this.generateAuthUrl(credentials); // For OAuth flows, return success: false with authUrl // This indicates that authentication is required return { success: false, authUrl, message: 'OAuth authentication required', }; } catch (error) { console.error('🚨 Fyers login failed:', error); return { success: false, message: error.message || 'Login failed', }; } } // Place order using official API async placeOrder(orderData) { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { // Fyers API v3 expects specific payload structure with mixed case field names const payload = { symbol: orderData.symbol, qty: Math.abs(orderData.qty), // Ensure quantity is positive type: this.getOrderTypeCode(orderData.type), side: orderData.side === 'BUY' ? 1 : -1, // 1=BUY, -1=SELL productType: this.getProductTypeCode(orderData.productType), // Keep camelCase limitPrice: orderData.limitPrice || 0, // Keep camelCase stopPrice: orderData.stopPrice || 0, // Keep camelCase disclosedQty: orderData.disclosedQty || 0, // Keep camelCase validity: orderData.validity === 'DAY' ? 'DAY' : 'IOC', offlineOrder: orderData.offlineOrder || false, // Keep camelCase stopLoss: orderData.stopLoss || 0, // Keep camelCase takeProfit: orderData.takeProfit || 0, // Keep camelCase }; console.log('🔄 Placing Fyers order with payload:', JSON.stringify(payload, null, 2)); const response = await this.fyers.place_order(payload); console.log('✅ Fyers order response:', response); return response; } catch (error) { console.error('🚨 Failed to place Fyers order:', error); throw new Error(error.message || 'Order placement failed'); } } // Cancel order using official API async cancelOrder(orderId) { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const payload = { id: orderId }; const response = await this.fyers.cancel_order(payload); console.log('✅ Order cancelled successfully:', response); return response; } catch (error) { console.error('🚨 Failed to cancel order:', error); throw new Error(error.message || 'Order cancellation failed'); } } // Modify order using official API async modifyOrder(orderId, modifications) { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const payload = { id: orderId, ...modifications }; const response = await this.fyers.modify_order(payload); console.log('✅ Order modified successfully:', response); return response; } catch (error) { console.error('🚨 Failed to modify order:', error); throw new Error(error.message || 'Order modification failed'); } } // Get order book using official API async getOrderBook() { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const response = await this.fyers.orderbook(); return response.orderBook || []; } catch (error) { console.error('🚨 Failed to get order book:', error); throw new Error(error.message || 'Failed to get order book'); } } // Get positions using official API async getPositions() { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const response = await this.fyers.get_positions(); return response.netPositions || []; } catch (error) { console.error('🚨 Failed to get positions:', error); throw new Error(error.message || 'Failed to get positions'); } } // Search symbols using official API async searchScrip(exchange, symbol) { try { const response = await this.fyers.search_scrips({ symbol: `${exchange}:${symbol}` }); return response.symbols || []; } catch (error) { console.error('🚨 Failed to search symbols:', error); throw new Error(error.message || 'Symbol search failed'); } } // Get quotes using official API async getQuotes(symbols) { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const response = await this.fyers.getQuotes(symbols); return response.d || []; } catch (error) { console.error('🚨 Failed to get quotes:', error); throw new Error(error.message || 'Failed to get quotes'); } } // Get profile using official API async getProfile() { if (!this.accessToken) { throw new Error('Not authenticated. Please login first.'); } try { const response = await this.fyers.get_profile(); return response; } catch (error) { console.error('🚨 Failed to get profile:', error); throw new Error(error.message || 'Failed to get profile'); } } // Helper method to convert order type to code getOrderTypeCode(orderType) { const orderTypeMap = { 'LIMIT': 1, 'MARKET': 2, 'SL': 3, // Stop Loss 'SL-M': 4, // Stop Loss Market }; return orderTypeMap[orderType] || 2; // Default to MARKET } // Helper method to convert product type to code getProductTypeCode(productType) { const productMap = { 'CNC': 'CNC', // Cash and Carry 'INTRADAY': 'INTRADAY', // Intraday/MIS 'MIS': 'INTRADAY', // Map MIS to INTRADAY 'MARGIN': 'MARGIN', // Margin/NRML 'NRML': 'MARGIN', // Map NRML to MARGIN 'CO': 'CO', // Cover Order 'BO': 'BO', // Bracket Order }; return productMap[productType] || 'CNC'; } // Check if authenticated isAuthenticated() { return !!this.accessToken; } // Get access token getAccessToken() { return this.accessToken; } // Set access token (for existing sessions) setAccessToken(token) { this.accessToken = token; this.fyers.setAccessToken(token); } // Refresh access token using refresh token async refreshAccessToken(refreshToken) { try { console.log('🔄 Refreshing Fyers access token using refresh token...'); // Create appIdHash (SHA-256 of appId + secretKey) const crypto = require('crypto'); const appIdHash = crypto.createHash('sha256').update(this.clientId + this.secretKey).digest('hex'); const response = await fetch('https://api-t1.fyers.in/api/v3/validate-refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken, appIdHash: appIdHash }) }); const data = await response.json(); if (data.s === 'ok') { this.accessToken = data.access_token; this.refreshToken = refreshToken; // Keep the same refresh token this.fyers.setAccessToken(data.access_token); console.log('✅ Fyers access token refreshed successfully'); // Calculate new expiry time (24 hours from now) const expiryTime = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); return { success: true, accessToken: data.access_token, refreshToken: refreshToken, // Same refresh token message: 'Access token refreshed successfully', expiryTime: expiryTime }; } else { console.error('❌ Fyers token refresh failed:', data.message); return { success: false, message: data.message || 'Token refresh failed' }; } } catch (error) { console.error('🚨 Fyers token refresh error:', error); return { success: false, message: error.message || 'Token refresh failed' }; } } // Validate if the current session is still active async validateSession() { if (!this.accessToken) { return false; } try { // Use a lightweight API call to check if session is still valid // getProfile is a simple endpoint that requires authentication const response = await this.fyers.get_profile(); // If the call succeeds, session is valid return response.s === 'ok'; } catch (error) { console.log('⚠️ Session validation failed for Fyers:', error.message); // If API call fails, session is likely expired this.accessToken = null; return false; } } // Logout async logout() { try { this.accessToken = null; this.appId = ''; console.log('✅ Fyers logout successful'); return { success: true, message: 'Logout successful', }; } catch (error) { console.error('🚨 Fyers logout failed:', error); return { success: false, message: error.message || 'Logout failed', }; } } } exports.FyersService = FyersService; //# sourceMappingURL=fyersService.js.map