import { SuperstoreAuth } from './Auth.js';
import { DirectAuth } from './DirectAuth.js';
import { SessionInfo } from './types.js';
/**
* Global session manager for maintaining authentication state across MCP server lifetime
*/
export class SessionManager {
private static instance: SessionManager;
private auth: SuperstoreAuth | DirectAuth | null = null;
private sessionInfo: SessionInfo = { isAuthenticated: false };
private lastActivity: Date = new Date();
private sessionTimeout: number = 3600000; // 1 hour
private refreshThreshold: number = 300000; // 5 minutes before expiry
private isRefreshing: boolean = false;
private constructor() {}
static getInstance(): SessionManager {
if (!SessionManager.instance) {
SessionManager.instance = new SessionManager();
}
return SessionManager.instance;
}
/**
* Get or create authenticated session
*/
async getAuthenticatedSession(): Promise<SuperstoreAuth | DirectAuth> {
// Check if we have a valid session
if (this.auth && await this.isSessionValid()) {
this.updateLastActivity();
return this.auth;
}
// If session is about to expire, refresh it
if (this.auth && this.isSessionNearExpiry()) {
await this.refreshSession();
return this.auth;
}
// Create new session
await this.createNewSession();
return this.auth!;
}
/**
* Check if current session is valid
*/
private async isSessionValid(): Promise<boolean> {
if (!this.auth || !this.sessionInfo.isAuthenticated) {
return false;
}
// Check if session has timed out
const now = new Date();
const timeSinceLastActivity = now.getTime() - this.lastActivity.getTime();
if (timeSinceLastActivity > this.sessionTimeout) {
this.sessionInfo = { isAuthenticated: false };
return false;
}
// Verify session is still valid with the server
try {
const isValid = await this.auth.isAuthenticated();
if (!isValid) {
this.sessionInfo = { isAuthenticated: false };
return false;
}
return true;
} catch (error) {
console.error('Session validation error:', error);
this.sessionInfo = { isAuthenticated: false };
return false;
}
}
/**
* Check if session is near expiry
*/
private isSessionNearExpiry(): boolean {
if (!this.sessionInfo.sessionExpiry) return false;
const now = new Date();
const timeUntilExpiry = this.sessionInfo.sessionExpiry.getTime() - now.getTime();
return timeUntilExpiry < this.refreshThreshold;
}
/**
* Create new authenticated session
*/
private async createNewSession(): Promise<void> {
try {
// Use DirectAuth if bearer token is provided in env
const hasBearerToken = process.env.SUPERSTORE_BEARER_TOKEN && process.env.SUPERSTORE_BEARER_TOKEN.startsWith('eyJ');
if (hasBearerToken) {
console.log('Creating DirectAuth session with bearer token');
this.auth = new DirectAuth();
} else {
console.log('Creating Puppeteer Auth session');
this.auth = new SuperstoreAuth();
}
const success = await this.auth.login();
if (success) {
this.sessionInfo = this.auth.getSessionInfo();
this.updateLastActivity();
} else {
throw new Error('Failed to create new session');
}
} catch (error) {
console.error('Failed to create new session:', error);
this.auth = null;
this.sessionInfo = { isAuthenticated: false };
throw error;
}
}
/**
* Refresh current session
*/
private async refreshSession(): Promise<void> {
if (this.isRefreshing) {
// Wait for ongoing refresh to complete
while (this.isRefreshing) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return;
}
this.isRefreshing = true;
try {
if (this.auth) {
const success = await this.auth.refreshSession();
if (success) {
this.sessionInfo = this.auth.getSessionInfo();
this.updateLastActivity();
} else {
throw new Error('Session refresh failed');
}
} else {
await this.createNewSession();
}
} catch (error) {
console.error('Session refresh failed:', error);
// Try to create a new session
try {
await this.createNewSession();
} catch (createError) {
console.error('Failed to create new session after refresh failure:', createError);
this.auth = null;
this.sessionInfo = { isAuthenticated: false };
throw createError;
}
} finally {
this.isRefreshing = false;
}
}
/**
* Update last activity timestamp
*/
private updateLastActivity(): void {
this.lastActivity = new Date();
}
/**
* Get current session info
*/
getSessionInfo(): SessionInfo {
return { ...this.sessionInfo };
}
/**
* Check if currently authenticated
*/
async isAuthenticated(): Promise<boolean> {
return await this.isSessionValid();
}
/**
* Logout and clear session
*/
async logout(): Promise<void> {
try {
if (this.auth) {
await this.auth.logout();
}
} catch (error) {
console.error('Logout error:', error);
} finally {
this.auth = null;
this.sessionInfo = { isAuthenticated: false };
this.lastActivity = new Date();
}
}
/**
* Cleanup resources
*/
async cleanup(): Promise<void> {
await this.logout();
}
/**
* Get session statistics
*/
getSessionStats(): {
isAuthenticated: boolean;
lastActivity: Date;
sessionExpiry?: Date;
timeSinceLastActivity: number;
isNearExpiry: boolean;
} {
const now = new Date();
const timeSinceLastActivity = now.getTime() - this.lastActivity.getTime();
return {
isAuthenticated: this.sessionInfo.isAuthenticated,
lastActivity: this.lastActivity,
sessionExpiry: this.sessionInfo.sessionExpiry,
timeSinceLastActivity,
isNearExpiry: this.isSessionNearExpiry()
};
}
}