@copytrade/unified-broker
Version:
Unified broker interface library for Indian stock market brokers with plugin architecture
371 lines • 14.8 kB
JavaScript
"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