WhatapClient.ts•8.15 kB
/**
* WhatapClient - WhaTap API Client
*
* Handles API requests to WhaTap service with authentication
*/
import axios, { AxiosInstance } from 'axios';
import type {
MxqlRequest,
MxqlParams,
WhaTapResponse,
Project,
QueryOptions,
} from '../types';
import { ApiError } from '../types';
import { AuthManager } from '../auth/AuthManager';
/**
* WhatapClient handles API calls to WhaTap service
*/
export class WhatapClient {
private axios: AxiosInstance;
private authManager: AuthManager;
private serviceUrl: string;
constructor(authManager: AuthManager) {
this.authManager = authManager;
const session = authManager.getSession();
this.serviceUrl = session.serviceUrl;
// Create axios instance (no need for separate cookie jar - we'll use cookies from AuthManager)
this.axios = axios.create({
baseURL: this.serviceUrl,
timeout: 30000,
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'WhatapMxqlCLI/1.0.0',
},
withCredentials: true,
validateStatus: (status) => true, // Accept all status codes (like mobile app)
});
// Add request interceptor to inject API token
this.axios.interceptors.request.use(
(config) => {
const session = this.authManager.getSession();
if (session.apiToken) {
config.headers['x-whatap-token'] = session.apiToken;
}
return config;
},
(error) => Promise.reject(error)
);
}
/**
* Execute MXQL query
*
* @param options - Query options
* @returns Query result
* @throws {ApiError} If API call fails
*/
async executeMxql(options: QueryOptions): Promise<WhaTapResponse> {
const { pcode, mql, stime, etime, limit } = options;
// Build MXQL request
const mxqlRequest: MxqlRequest = {
type: 'mxql',
pcode,
params: {
mql,
stime: stime || Date.now() - 3600000, // Default: 1 hour ago
etime: etime || Date.now(),
limit: limit || 100,
},
path: 'text',
};
try {
// MXQL API requires cookie authentication (not API token)
const cookieHeader = this.authManager.getCookieHeader();
const response = await this.axios.post<any>(
'/yard/api/flush',
mxqlRequest,
{
headers: {
Cookie: cookieHeader,
},
}
);
return {
code: response.status,
msg: response.statusText,
data: response.data,
ok: response.status === 200,
};
} catch (error: any) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`MXQL execution failed: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`MXQL execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Get list of projects for the authenticated user
*
* @returns List of projects
* @throws {ApiError} If API call fails
*/
async getProjects(): Promise<Project[]> {
try {
const response = await this.axios.get<any>('/open/api/json/projects');
if (response.status !== 200) {
throw new ApiError(
`Failed to get projects: ${response.statusText}`,
response.status,
response.data
);
}
// Open API returns { data: Project[], total: number }
return response.data.data || response.data;
} catch (error: any) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`Failed to get projects: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`Failed to get projects: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Get project details by project code
*
* @param pcode - Project code
* @returns Project details
* @throws {ApiError} If API call fails
*/
async getProject(pcode: number): Promise<Project> {
try {
// Get all projects and find the matching one
// (Open API doesn't have a single project endpoint)
const projects = await this.getProjects();
const project = projects.find((p) => p.projectCode === pcode);
if (!project) {
throw new ApiError(
`Project with code ${pcode} not found`,
404
);
}
return project;
} catch (error: any) {
if (error instanceof ApiError) {
throw error;
}
if (axios.isAxiosError(error)) {
throw new ApiError(
`Failed to get project: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`Failed to get project: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Execute generic GET request
*
* @param url - API endpoint URL
* @returns Response data
* @throws {ApiError} If API call fails
*/
async get<T = any>(url: string): Promise<WhaTapResponse<T>> {
try {
const response = await this.axios.get<T>(url);
return {
code: response.status,
msg: response.statusText,
data: response.data,
ok: response.status === 200,
};
} catch (error: any) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`GET request failed: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`GET request failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Execute generic POST request
*
* @param url - API endpoint URL
* @param data - Request body
* @returns Response data
* @throws {ApiError} If API call fails
*/
async post<T = any>(url: string, data?: any): Promise<WhaTapResponse<T>> {
try {
const response = await this.axios.post<T>(url, data);
return {
code: response.status,
msg: response.statusText,
data: response.data,
ok: response.status === 200,
};
} catch (error: any) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`POST request failed: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`POST request failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Get list of available categories for a project
*
* @param pcode - Project code
* @param stime - Start time (Unix timestamp in milliseconds)
* @param etime - End time (Unix timestamp in milliseconds)
* @returns List of available categories
* @throws {ApiError} If API call fails
*/
async getCategories(pcode: number, stime?: number, etime?: number): Promise<WhaTapResponse> {
const now = Date.now();
const request = {
type: 'tagcount',
pcode,
params: {
stime: stime || now - 3600000, // Default: 1 hour ago
etime: etime || now,
},
path: 'categories',
};
try {
// Categories API requires cookie authentication
const cookieHeader = this.authManager.getCookieHeader();
const response = await this.axios.post<any>(
'/yard/api/flush',
request,
{
headers: {
Cookie: cookieHeader,
},
}
);
return {
code: response.status,
msg: response.statusText,
data: response.data,
ok: response.status === 200,
};
} catch (error: any) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`Failed to get categories: ${error.message}`,
error.response?.status,
error.response?.data
);
}
throw new ApiError(
`Failed to get categories: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
}