smartapi-typescript
Version:
TypeScript library for Angel One SmartAPI broker API
421 lines • 17.3 kB
JavaScript
"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