superPrecioApi.ts•10.3 kB
/**
* HTTP Client for Superprecio API
*/
import axios, { AxiosInstance, AxiosError } from 'axios';
import type {
SuperPrecioApiConfig,
SearchRequest,
SearchResponse,
ApiError,
} from '../types/index.js';
export class SuperPrecioApiClient {
private client: AxiosInstance;
private config: SuperPrecioApiConfig;
private csrfToken: string | null = null;
constructor(config: SuperPrecioApiConfig) {
this.config = config;
this.client = axios.create({
baseURL: config.baseUrl,
timeout: config.timeout,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
withCredentials: true, // Important for cookies/sessions
});
// Add request interceptor for debugging
if (config.debug) {
this.client.interceptors.request.use((request) => {
console.error('[SuperPrecio API] Request:', {
method: request.method,
url: request.url,
data: request.data,
});
return request;
});
this.client.interceptors.response.use(
(response) => {
console.error('[SuperPrecio API] Response:', {
status: response.status,
data: response.data,
});
return response;
},
(error) => {
console.error('[SuperPrecio API] Error:', error.message);
return Promise.reject(error);
}
);
}
}
/**
* Get CSRF token from the server
*/
private async getCsrfToken(): Promise<string | null> {
try {
const response = await this.client.get('/');
// Try to extract CSRF token from cookies or headers
const cookies = response.headers['set-cookie'];
if (cookies) {
const csrfCookie = cookies.find((cookie: string) => cookie.includes('XSRF-TOKEN'));
if (csrfCookie) {
const match = csrfCookie.match(/XSRF-TOKEN=([^;]+)/);
if (match) {
return decodeURIComponent(match[1]);
}
}
}
return null;
} catch (error) {
if (this.config.debug) {
console.error('[SuperPrecio API] Failed to get CSRF token:', error);
}
return null;
}
}
/**
* Search products by description or EAN code
*/
async searchProducts(params: SearchRequest): Promise<SearchResponse> {
try {
const response = await this.client.post<SearchResponse>('/api/products', params);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Search products by code/barcode
*/
async searchByCode(code: string): Promise<SearchResponse> {
try {
const response = await this.client.post<SearchResponse>('/api/productsByCode', {
search: code,
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Subscribe device to notifications
*/
async subscribeDevice(token: string): Promise<any> {
try {
const response = await this.client.post('/devices/suscribe', { token });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Send notification to specific device
*/
async sendNotification(data: {
token: string;
title: string;
body: string;
data?: Record<string, any>;
}): Promise<any> {
try {
const response = await this.client.post('/notifications/send', data);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Broadcast notification to all devices
*/
async broadcastNotification(data: {
title: string;
body: string;
data?: Record<string, any>;
}): Promise<any> {
try {
const response = await this.client.post('/notifications/broadcast', data);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Health check - verify API is accessible
*/
async healthCheck(): Promise<boolean> {
try {
await this.client.get('/');
return true;
} catch (error) {
return false;
}
}
// ============================================
// MCP V2 Features - Shopping Lists
// ============================================
/**
* Get all shopping lists
*/
async getShoppingLists(params?: { userId?: number }): Promise<any> {
try {
const response = await this.client.get('/api/lists', { params });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Create a new shopping list
*/
async createShoppingList(data: {
name: string;
description?: string;
userId?: number;
items?: Array<{
productName: string;
barcode?: string;
quantity?: number;
notes?: string;
}>;
}): Promise<any> {
try {
const response = await this.client.post('/api/lists', data);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get a specific shopping list
*/
async getShoppingList(id: number): Promise<any> {
try {
const response = await this.client.get(`/api/lists/${id}`);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Delete a shopping list
*/
async deleteShoppingList(id: number): Promise<any> {
try {
const response = await this.client.delete(`/api/lists/${id}`);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Add items to a shopping list
*/
async addItemsToList(
id: number,
items: Array<{
productName: string;
barcode?: string;
quantity?: number;
notes?: string;
}>
): Promise<any> {
try {
const response = await this.client.post(`/api/lists/${id}/items`, { items });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Optimize shopping list - find best supermarket
*/
async optimizeShoppingList(id: number): Promise<any> {
try {
const response = await this.client.post(`/api/lists/${id}/optimize`);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
// ============================================
// MCP V2 Features - Price Alerts
// ============================================
/**
* Get all price alerts
*/
async getPriceAlerts(params?: { userId?: number; isActive?: boolean }): Promise<any> {
try {
const response = await this.client.get('/api/alerts', { params });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Create a price alert
*/
async createPriceAlert(data: {
productName: string;
barcode?: string;
targetPrice: number;
userId?: number;
notifyEnabled?: boolean;
}): Promise<any> {
try {
const response = await this.client.post('/api/alerts', data);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Update a price alert
*/
async updatePriceAlert(
id: number,
data: {
targetPrice?: number;
isActive?: boolean;
notifyEnabled?: boolean;
}
): Promise<any> {
try {
const response = await this.client.put(`/api/alerts/${id}`, data);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Delete a price alert
*/
async deletePriceAlert(id: number): Promise<any> {
try {
const response = await this.client.delete(`/api/alerts/${id}`);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Check price alerts - verify current prices
*/
async checkPriceAlerts(params?: { userId?: number; alertId?: number }): Promise<any> {
try {
const response = await this.client.post('/api/alerts/check', params);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
// ============================================
// MCP V2 Features - Locations
// ============================================
/**
* Find nearby supermarkets
*/
async findNearbySupermarkets(params: {
lat: number;
lng: number;
radius?: number;
}): Promise<any> {
try {
const response = await this.client.get('/api/locations/nearby', { params });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Get all locations
*/
async getLocations(params?: { supermarketId?: number; city?: string }): Promise<any> {
try {
const response = await this.client.get('/api/locations', { params });
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
/**
* Handle API errors
*/
private handleError(error: unknown): ApiError {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<any>;
if (axiosError.response) {
// Server responded with error status
return {
message: (axiosError.response.data && axiosError.response.data.message) || axiosError.message,
code: `HTTP_${axiosError.response.status}`,
details: axiosError.response.data,
};
} else if (axiosError.request) {
// Request was made but no response received
return {
message: 'No response from Superprecio API. Is the server running?',
code: 'NO_RESPONSE',
details: { baseUrl: this.config.baseUrl },
};
}
}
// Generic error
return {
message: error instanceof Error ? error.message : 'Unknown error occurred',
code: 'UNKNOWN_ERROR',
details: error,
};
}
}
/**
* Create API client from environment variables
*/
export function createApiClient(): SuperPrecioApiClient {
const config: SuperPrecioApiConfig = {
baseUrl: process.env.SUPERPRECIO_API_URL || 'https://superprecio.ar',
timeout: parseInt(process.env.REQUEST_TIMEOUT || '30000', 10),
debug: process.env.DEBUG === 'true',
};
return new SuperPrecioApiClient(config);
}