UNPKG

tradestation-api-ts

Version:

A comprehensive TypeScript wrapper for TradeStation WebAPI v3

128 lines (127 loc) 4.74 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenManager = void 0; const axios_1 = __importDefault(require("axios")); class TokenManager { constructor(config) { this.accessToken = null; this.refreshToken = null; this.tokenExpiry = null; this.refreshing = null; const clientId = config?.clientId || process.env.CLIENT_ID; const clientSecret = config?.clientSecret || process.env.CLIENT_SECRET; if (!clientId || !clientSecret) { throw new Error('Client ID and Client Secret are required'); } this.config = { clientId, clientSecret, maxConcurrentStreams: config?.maxConcurrentStreams, }; // Set initial refresh token if provided in config if (config?.refresh_token) { this.refreshToken = config.refresh_token; } this.axiosInstance = axios_1.default.create({ baseURL: 'https://signin.tradestation.com', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); } shouldRefreshToken() { if (!this.tokenExpiry) return true; // Refresh if less than REFRESH_THRESHOLD milliseconds remaining return Date.now() >= (this.tokenExpiry - TokenManager.REFRESH_THRESHOLD); } /** * Gets a valid access token, refreshing it if necessary. * This is the main method that should be used to get an access token. * @returns A promise that resolves to a valid access token * @throws Error if unable to get a valid token */ async getValidAccessToken() { if (this.shouldRefreshToken()) { if (this.refreshToken) { await this.refreshAccessToken(); } else { throw new Error('No refresh token available. You must provide a refresh token in the client configuration.'); } } return this.getAccessToken(); } /** * Refreshes the access token using the refresh token. * If the response includes a new refresh token, it will be stored for future use. * @throws Error if refresh fails or no refresh token is available */ async refreshAccessToken() { if (!this.refreshToken) { throw new Error('No refresh token available'); } // If already refreshing, wait for that to complete if (this.refreshing) { await this.refreshing; return; } const refreshToken = this.refreshToken; // Capture current refresh token this.refreshing = (async () => { const params = new URLSearchParams({ grant_type: 'refresh_token', client_id: this.config.clientId, client_secret: this.config.clientSecret, refresh_token: refreshToken, }); try { const response = await this.axiosInstance.post('/oauth/token', params); this.updateTokens(response.data); } catch (error) { if (axios_1.default.isAxiosError(error) && error.response?.data) { const apiError = error.response.data; throw new Error(`Token refresh failed: ${apiError.error_description || apiError.error}`); } throw error; } finally { this.refreshing = null; } })(); await this.refreshing; } updateTokens(authResponse) { this.accessToken = authResponse.access_token; // Update refresh token only if a new one is provided if (authResponse.refresh_token) { this.refreshToken = authResponse.refresh_token; } this.tokenExpiry = Date.now() + (authResponse.expires_in * 1000); } /** * Returns the current refresh token * @returns The current refresh token or null if none is available */ getRefreshToken() { return this.refreshToken; } getAccessToken() { if (!this.accessToken) { throw new Error('No access token available'); } return this.accessToken; } isTokenExpired() { return this.tokenExpiry ? Date.now() >= this.tokenExpiry : true; } hasValidToken() { return !!this.accessToken && !this.isTokenExpired(); } } exports.TokenManager = TokenManager; // 5 minutes in milliseconds TokenManager.REFRESH_THRESHOLD = 5 * 60 * 1000;