@sudowealth/schwab-api
Version:
TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety
106 lines (105 loc) • 3.67 kB
JavaScript
/**
* Centralized token validation utilities
*/
/**
* Validate TokenData (partial token information)
* This handles cases where refresh token or expiration might be optional
*/
export function validateTokenData(tokenData) {
// Basic validation
if (!tokenData || typeof tokenData !== 'object') {
return false;
}
// Access token is always required
if (!tokenData.accessToken || typeof tokenData.accessToken !== 'string') {
return false;
}
// If expiresAt is provided, it must be valid
if (tokenData.expiresAt !== undefined &&
(typeof tokenData.expiresAt !== 'number' || tokenData.expiresAt <= 0)) {
return false;
}
// If refreshToken is provided, it must be a string
if (tokenData.refreshToken !== undefined &&
typeof tokenData.refreshToken !== 'string') {
return false;
}
return true;
}
/**
* Perform detailed validation with comprehensive reporting
* @param tokenData The token data to validate
* @param refreshThresholdMs Time before expiration to consider token as expiring (default: 5 minutes)
* @returns Detailed validation result
*/
export function validateTokenDetailed(tokenData, refreshThresholdMs = 300_000) {
const now = Date.now();
const issues = [];
// Check if token exists
if (!tokenData.accessToken) {
return {
valid: false,
reason: 'No access token available',
format: { isValid: false, issues: ['Missing access token'] },
};
}
// Check if token is expired
if (!tokenData.expiresAt || tokenData.expiresAt <= now) {
return {
valid: false,
reason: 'Token is expired',
canRefresh: !!tokenData.refreshToken,
isExpiring: true,
expiresInSeconds: tokenData.expiresAt
? Math.floor((tokenData.expiresAt - now) / 1000)
: 0,
format: { isValid: true, issues: [] },
};
}
// Check if token is about to expire
const expiresInMs = tokenData.expiresAt - now;
const isExpiring = expiresInMs <= refreshThresholdMs;
// Format validation
let formatValid = true;
// Basic token validation checks
if (typeof tokenData.accessToken !== 'string' ||
tokenData.accessToken.length < 10) {
issues.push('Access token appears malformed');
formatValid = false;
}
if (tokenData.refreshToken && typeof tokenData.refreshToken !== 'string') {
issues.push('Refresh token is not a string');
formatValid = false;
}
return {
valid: !isExpiring && formatValid,
reason: isExpiring ? 'Token is nearing expiration' : undefined,
canRefresh: !!tokenData.refreshToken,
isExpiring,
expiresInSeconds: Math.floor(expiresInMs / 1000),
format: {
isValid: formatValid,
issues: issues.length > 0 ? issues : undefined,
},
};
}
/**
* Check if a token is expired or expiring soon
* @param expiresAt The expiration timestamp
* @param thresholdMs Time before expiration to consider token as expiring
*/
export function isTokenExpiring(expiresAt, thresholdMs = 300_000) {
if (!expiresAt)
return true;
return expiresAt <= Date.now() + thresholdMs;
}
/**
* Ensure TokenData has all required fields with defaults
*/
export function ensureCompleteTokenData(tokenData, defaultExpiresInSeconds = 3600) {
return {
accessToken: tokenData.accessToken || '',
refreshToken: tokenData.refreshToken || '',
expiresAt: tokenData.expiresAt || Date.now() + defaultExpiresInSeconds * 1000,
};
}