import crypto from 'crypto'; export interface AuthLoginResponse { accessToken: string; mdAccessToken: string; expirationTime: string; userStatus: string; userId: number; name: string; hasLive: boolean; hasSimPlus: boolean; hasFunded: boolean; hasMarketData: boolean; requiredNonProCertification: boolean; outdatedSentimentPolicy: boolean; orgName: string; /** Where this user's organization is served — see demoHosts(). */ apiHosts?: TradovateApiHosts | null; } /** The hosts a login answer names, one per environment and service. */ export interface TradovateApiHosts { live?: string; demo?: string; mdLive?: string; mdDemo?: string; reportingLive?: string; reportingDemo?: string; replay?: string; } export const SHARED_DEMO_HOST = 'demo.tradovateapi.com'; export const SHARED_DEMO_REPORTING_HOST = 'rpt-demo.tradovateapi.com'; const HOST_NAME = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i; /** * The hosts a login's demo-environment calls go to: REST and the trading * socket (service), and reports. Tradovate serves some organizations from * their own hosts and says so in the login answer's apiHosts — Lucid moved to * demo-api.lucid.ninjatrader.com (seen 2026-11-01), and the shared demo * socket drops a Lucid session right after its authorize. A login without * apiHosts, or with a value that isn't a plain host name, keeps the shared * hosts. */ export function demoHosts(apiHosts: TradovateApiHosts | null | undefined): { service: string; reporting: string } { const pick = (value: unknown, fallback: string) => typeof value === 'string' && HOST_NAME.test(value) ? value.toLowerCase() : fallback; return { service: pick(apiHosts?.demo, SHARED_DEMO_HOST), reporting: pick(apiHosts?.reportingDemo, SHARED_DEMO_REPORTING_HOST), }; } export interface AccountItem { id: number; name: string; userId: number; accountType: string; restricted: boolean; closed: boolean; clearingHouseId: number; riskCategoryId: number; autoLiqProfileId: number; marginAccountType: string; legalStatus: string; archived: boolean; timestamp: string; active: boolean; } export interface Contract { id: number; name: string; contractMaturityId: number; timestamp: string; status: string; providerTickSize: number; } export const randomUUIDV4 = () => { return crypto.randomUUID(); }; export const computeSec = (user: string, password: string, deviceId: string, chl: string) => { const key = '035a1259-11e7-485a-aeae-9b6016579351'; const Ct = ['chl', 'deviceId', 'name', 'password', 'appId']; const body = { chl: chl, deviceId: deviceId, name: user, password: password, appId: 'tradovate_trader(web)', }; const msg = Ct.map((k) => body[k] ?? '').join(''); return crypto.createHmac('sha256', key).update(msg, 'utf8').digest('hex'); };