UNPKG

smartapi-typescript

Version:

TypeScript library for Angel One SmartAPI broker API

421 lines 17.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Auth = void 0; const apiUrls_1 = require("../../constants/apiUrls"); const http = __importStar(require("../../utils/http")); const otplib_1 = require("otplib"); /** * Authentication module for SmartAPI * Handles login, session management, token refresh and logout */ class Auth { /** * Initialize authentication module */ constructor(config, httpClient, debug = false) { this.lastTokenRefresh = 0; this.minRefreshInterval = 60000; // Minimum 1 minute between token refresh attempts this.apiKey = config.apiKey; this.clientId = config.clientId; this.jwtToken = config.jwtToken; this.refreshToken = config.refreshToken; this.debug = debug; this.httpClient = httpClient; this.totpSecret = config.totpSecret; } /** * Generate a TOTP code using the configured TOTP secret * @returns Generated TOTP code or undefined if no secret is set */ generateTOTP() { if (!this.totpSecret) { this.log('No TOTP secret configured'); return undefined; } try { const token = otplib_1.authenticator.generate(this.totpSecret); this.log('TOTP generated successfully'); return token; } catch (error) { this.log('Failed to generate TOTP', error); return undefined; } } /** * Set the TOTP secret key * @param secret The TOTP secret key */ setTOTPSecret(secret) { this.totpSecret = secret; this.log('TOTP secret configured'); } /** * Get common headers required for API requests * Headers conform to the SmartAPI documentation requirements * @param options Optional parameters to customize headers * @returns Headers object for API requests */ getHeaders(options) { const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-UserType': 'USER', 'X-SourceID': 'WEB', 'X-ClientLocalIP': (options === null || options === void 0 ? void 0 : options.clientLocalIP) || '127.0.0.1', 'X-ClientPublicIP': (options === null || options === void 0 ? void 0 : options.clientPublicIP) || '127.0.0.1', 'X-MACAddress': (options === null || options === void 0 ? void 0 : options.macAddress) || '00:00:00:00:00:00', 'X-PrivateKey': this.apiKey }; // Add authorization header if jwt token is available if (this.jwtToken) { headers['Authorization'] = `Bearer ${this.jwtToken}`; } return headers; } /** * Log debug messages if debug mode is enabled * @param message Message to log * @param data Optional data to log */ log(message, data) { if (this.debug) { console.log(`[SmartAPI:Auth] ${message}`); if (data) { console.log(data); } } } /** * Check if user is authenticated with a valid token * @returns Boolean indicating if authenticated */ isAuthenticated() { return !!this.jwtToken; } /** * Get the JWT token * @returns Current JWT token */ getJwtToken() { return this.jwtToken; } /** * Get the feed token for WebSocket connections * @returns Feed token */ getFeedToken() { return this.feedToken; } /** * Get the refresh token * @returns Current refresh token */ getRefreshToken() { return this.refreshToken; } /** * Set tokens received from external sources (like publisher login) * @param jwtToken JWT token * @param refreshToken Refresh token * @param feedToken Feed token */ setTokens(jwtToken, refreshToken, feedToken) { if (jwtToken) this.jwtToken = jwtToken; if (refreshToken) this.refreshToken = refreshToken; if (feedToken) this.feedToken = feedToken; } /** * Authenticate user with Angel One API using password * @param password User's password * @param totp TOTP code from authenticator app for two-factor authentication * (if not provided and totpSecret is set, will be generated automatically) * @param state Optional state variable for external applications * @param options Network configuration options * @returns Authentication result containing jwt token, refresh token and feed token */ login(password, totp, state, options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (!this.clientId) { return { status: false, message: 'Client ID is not set. Please provide a client ID in the constructor.' }; } const payload = { clientcode: this.clientId, password, }; // First check if TOTP was provided directly in the function call if (totp) { payload.totp = totp; this.log('Using provided TOTP code'); } // If no TOTP was provided, try to generate it from secret else if (this.totpSecret) { const generatedTotp = this.generateTOTP(); if (generatedTotp) { payload.totp = generatedTotp; this.log('Using auto-generated TOTP code from secret'); } else { this.log('Failed to generate TOTP despite having secret'); } } else { this.log('No TOTP code provided or secret configured'); } // Add state parameter if provided if (state) { payload.state = state; } this.log('Attempting login', { clientId: this.clientId }); try { const response = yield http.post(`${apiUrls_1.API_URLS.BASE_URL}${apiUrls_1.API_URLS.LOGIN}`, payload, this.getHeaders(options)); if (response.status) { // Store the state variable if it was returned in the response if ((_a = response.data) === null || _a === void 0 ? void 0 : _a.state) { this.log('State variable returned in login response', { state: response.data.state }); } // Generate session with received tokens return this.generateSession((_b = response.data) === null || _b === void 0 ? void 0 : _b.jwtToken, (_c = response.data) === null || _c === void 0 ? void 0 : _c.refreshToken); } return response; } catch (error) { this.log('Login failed', error); return http.handleApiError(error); } }); } /** * Generate a new session using refresh token * @param jwtToken JWT token (optional if already set in constructor) * @param refreshToken Refresh token (optional if already set in constructor) * @param options Network configuration options * @returns Session data containing new jwtToken, refreshToken and feedToken */ generateSession(jwtToken, refreshToken, options) { return __awaiter(this, void 0, void 0, function* () { // Use provided tokens or fall back to instance variables const jwt = jwtToken || this.jwtToken; const refresh = refreshToken || this.refreshToken; if (!refresh) { return { status: false, message: 'Refresh token is required for generating a new session' }; } // Set or update tokens in instance if (jwt) { this.jwtToken = jwt; } this.refreshToken = refresh; this.log('Generating session with refresh token', { refreshToken: refresh }); try { const response = yield http.post(`${apiUrls_1.API_URLS.BASE_URL}${apiUrls_1.API_URLS.GENERATE_TOKEN}`, { refreshToken: refresh }, this.getHeaders(options)); if (response.status && response.data) { // Update tokens with new session this.jwtToken = response.data.jwtToken; this.refreshToken = response.data.refreshToken; this.feedToken = response.data.feedToken; this.lastTokenRefresh = Date.now(); this.log('Token refresh successful', { jwtTokenReceived: !!response.data.jwtToken, refreshTokenReceived: !!response.data.refreshToken, feedTokenReceived: !!response.data.feedToken }); } else { this.log('Token refresh failed with API error', response); } return response; } catch (error) { this.log('Generate session failed', error); return http.handleApiError(error); } }); } /** * Logout the current user session * @param options Network configuration options * @returns Logout result */ logout(options) { return __awaiter(this, void 0, void 0, function* () { if (!this.jwtToken) { return { status: false, message: 'Not logged in' }; } if (!this.clientId) { return { status: false, message: 'Client ID not set. Cannot logout without client ID.' }; } this.log('Attempting logout'); try { // API requires clientcode parameter in the payload const payload = { clientcode: this.clientId }; const response = yield http.post(`${apiUrls_1.API_URLS.BASE_URL}${apiUrls_1.API_URLS.LOGOUT}`, payload, this.getHeaders(options)); if (response.status) { // Clear tokens on successful logout this.jwtToken = undefined; this.refreshToken = undefined; this.feedToken = undefined; } return response; } catch (error) { this.log('Logout failed', error); return http.handleApiError(error); } }); } /** * Get user profile information * @param options Network configuration options * @returns User profile data */ getProfile(options) { return __awaiter(this, void 0, void 0, function* () { if (!this.jwtToken) { return { status: false, message: 'Not authenticated. Please login first.' }; } this.log('Fetching user profile'); try { return yield http.get(`${apiUrls_1.API_URLS.BASE_URL}${apiUrls_1.API_URLS.USER_PROFILE}`, this.getHeaders(options)); } catch (error) { this.log('Get profile failed', error); // Check if it's an authentication error and try to refresh token const errorResponse = http.handleApiError(error); if (errorResponse.errorcode && (errorResponse.errorcode === 'AG8002' || errorResponse.errorcode === 'AB8051') && this.refreshToken && Date.now() - this.lastTokenRefresh > this.minRefreshInterval) { this.log('Token expired, attempting refresh'); try { // Try to refresh the token const refreshResult = yield this.generateSession(); if (refreshResult.status) { this.log('Token refreshed successfully, retrying operation'); // Token refreshed, retry the original operation return this.getProfile(options); } } catch (refreshError) { this.log('Token refresh failed', refreshError); } } return errorResponse; } }); } /** * Generate a publisher login URL for redirecting users to the SmartAPI login endpoint * @param redirectUrl URL to redirect after successful login (must be registered in your MyApps settings) * @param state Optional state variable to track session (will be returned in the redirect) * @returns The URL to redirect users for login */ getPublisherLoginUrl(redirectUrl, state) { // Use the exact URL format specified in the documentation: https://smartapi.angelone.in/publisher-login?api_key=xxx&state=statevariable const baseUrl = 'https://smartapi.angelone.in/publisher-login'; const queryParams = new URLSearchParams(); queryParams.append('api_key', this.apiKey); if (state) { queryParams.append('state', state); } // After successful authentication, user will be redirected to this URL if (redirectUrl) { queryParams.append('redirect_url', redirectUrl); } return `${baseUrl}?${queryParams.toString()}`; } /** * Handle API errors, attempting to refresh token if appropriate * @param error Original error * @param retryFn Function to retry after token refresh * @returns API response */ handleApiError(error, retryFn) { return __awaiter(this, void 0, void 0, function* () { const errorResponse = http.handleApiError(error); // Check if token expired and we should refresh if (retryFn && errorResponse.errorcode && (errorResponse.errorcode === 'AG8002' || errorResponse.errorcode === 'AB8051') && this.refreshToken && Date.now() - this.lastTokenRefresh > this.minRefreshInterval) { this.log('Token expired, attempting refresh'); try { // Try to refresh the token const refreshResult = yield this.generateSession(); if (refreshResult.status) { this.log('Token refreshed successfully, retrying operation'); // Token refreshed, retry the original operation return retryFn(); } } catch (refreshError) { this.log('Token refresh failed', refreshError); } } return errorResponse; }); } } exports.Auth = Auth; //# sourceMappingURL=index.js.map