/**
* Joker.com DMAPI Client
*
* Handles authentication and API requests to Joker.com's Domain Management API.
* Supports both API key and username/password authentication.
*/
const DMAPI_BASE_URL = 'https://dmapi.joker.com/request';
/**
* DMAPI session management
*/
class DmapiSession {
private authSid: string | null = null;
private expiresAt: number = 0;
private sessionDuration: number = 3600000; // 1 hour in milliseconds
getAuthSid(): string | null {
// Check if session is still valid
if (this.authSid && Date.now() < this.expiresAt) {
return this.authSid;
}
// Session expired
this.authSid = null;
return null;
}
setAuthSid(authSid: string): void {
this.authSid = authSid;
this.expiresAt = Date.now() + this.sessionDuration;
}
clearSession(): void {
this.authSid = null;
this.expiresAt = 0;
}
isValid(): boolean {
return this.authSid !== null && Date.now() < this.expiresAt;
}
}
/**
* Singleton session instance
*/
const session = new DmapiSession();
/**
* Login to DMAPI and obtain auth-sid
*/
export async function dmapiLogin(): Promise<string> {
const apiKey = process.env.MCP_JOKER_API_KEY;
const username = process.env.MCP_JOKER_USERNAME;
const password = process.env.MCP_JOKER_PASSWORD;
// Check if we already have a valid session
const existingAuthSid = session.getAuthSid();
if (existingAuthSid) {
return existingAuthSid;
}
// Determine authentication method
let authParams: string;
if (apiKey) {
// Use API key (recommended)
authParams = `api-key=${encodeURIComponent(apiKey)}`;
} else if (username && password) {
// Fallback to username/password
authParams = `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`;
} else {
throw new Error(
'Joker.com DMAPI authentication not configured. ' +
'Set MCP_JOKER_API_KEY or MCP_JOKER_USERNAME + MCP_JOKER_PASSWORD in .env file.'
);
}
// Perform login request
const loginUrl = `${DMAPI_BASE_URL}/login?${authParams}`;
try {
const response = await fetch(loginUrl, {
method: 'GET',
headers: {
'User-Agent': 'mcp-domain-checker-price/1.0.0',
},
});
const responseText = await response.text();
if (!response.ok) {
throw new Error(`DMAPI login failed (HTTP ${response.status}): ${responseText}`);
}
// Parse response for Auth-Sid
// Response format is typically key-value pairs separated by newlines
const authSidMatch = responseText.match(/Auth-Sid:\s*(\S+)/i);
if (!authSidMatch) {
throw new Error(`Failed to extract Auth-Sid from DMAPI response: ${responseText}`);
}
const authSid = authSidMatch[1];
session.setAuthSid(authSid);
return authSid;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`DMAPI login error: ${message}`);
}
}
/**
* Make authenticated DMAPI request
*/
export async function dmapiRequest(
requestName: string,
params: Record<string, string> = {}
): Promise<string> {
// Ensure we're authenticated
const authSid = await dmapiLogin();
// Build query parameters
const queryParams = new URLSearchParams({
'auth-sid': authSid,
...params,
});
const requestUrl = `${DMAPI_BASE_URL}/${requestName}?${queryParams.toString()}`;
try {
const response = await fetch(requestUrl, {
method: 'GET',
headers: {
'User-Agent': 'mcp-domain-checker-price/1.0.0',
},
});
const responseText = await response.text();
if (!response.ok) {
// Check if session expired
if (responseText.includes('Auth-Sid') || responseText.includes('session')) {
// Clear session and retry once
session.clearSession();
return dmapiRequest(requestName, params); // Recursive retry with new session
}
throw new Error(`DMAPI request failed (HTTP ${response.status}): ${responseText}`);
}
return responseText;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`DMAPI request error for ${requestName}: ${message}`);
}
}
/**
* Logout and clear session
*/
export async function dmapiLogout(): Promise<void> {
const authSid = session.getAuthSid();
if (authSid) {
try {
await dmapiRequest('logout');
} catch (error) {
// Ignore logout errors, just clear session
console.error('DMAPI logout warning:', error);
}
}
session.clearSession();
}