UNPKG

@receeco/pos-agent

Version:

Receeco POS Integration Middleware Agent

234 lines 8.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OfflineQueueService = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const node_cron_1 = __importDefault(require("node-cron")); const logger_1 = require("../utils/logger"); const receipt_service_1 = require("./receipt-service"); const logger = (0, logger_1.createLogger)(); class OfflineQueueService { constructor() { this.isProcessing = false; this.isRender = process.env.RENDER === 'true'; this.queueFile = path_1.default.join(process.cwd(), "data", "offline-queue.json"); this.receiptService = new receipt_service_1.ReceiptService(); if (!this.isRender) { this.initializeQueue(); this.startPeriodicSync(); } else { logger.info("Offline queue disabled for Render production environment"); } } async initializeQueue() { try { const dataDir = path_1.default.dirname(this.queueFile); await promises_1.default.mkdir(dataDir, { recursive: true }); try { await promises_1.default.access(this.queueFile); } catch { await promises_1.default.writeFile(this.queueFile, JSON.stringify([])); } } catch (error) { logger.error("Failed to initialize offline queue:", error); } } async queueTransaction(transactionData) { if (this.isRender) { logger.warn("Transaction queuing skipped in Render environment"); return; } try { const queuedTransaction = { id: this.generateId(), data: transactionData, timestamp: new Date().toISOString(), retryCount: 0, maxRetries: 5, }; const queue = await this.loadQueue(); queue.push(queuedTransaction); await this.saveQueue(queue); logger.info("Transaction queued for offline processing:", { id: queuedTransaction.id, }); } catch (error) { logger.error("Failed to queue transaction:", error); throw error; } } async processQueue() { if (this.isRender) { return; } if (this.isProcessing) { logger.debug("Queue processing already in progress"); return; } this.isProcessing = true; try { const queue = await this.loadQueue(); const pendingTransactions = queue.filter((t) => t.retryCount < t.maxRetries); if (pendingTransactions.length === 0) { logger.debug("No pending transactions in queue"); return; } logger.info(`Processing ${pendingTransactions.length} queued transactions`); const results = await Promise.allSettled(pendingTransactions.map((transaction) => this.processQueuedTransaction(transaction))); const updatedQueue = []; const processedTransactionIds = []; for (let i = 0; i < results.length; i++) { const result = results[i]; const transaction = pendingTransactions[i]; if (result.status === "fulfilled") { logger.info("Queued transaction processed successfully:", { id: transaction.id, }); processedTransactionIds.push(transaction.id); } else { transaction.retryCount++; if (transaction.retryCount < transaction.maxRetries) { updatedQueue.push(transaction); logger.warn("Queued transaction failed, will retry:", { id: transaction.id, retryCount: transaction.retryCount, error: result.reason, }); } else { logger.error("Queued transaction exceeded max retries:", { id: transaction.id, }); processedTransactionIds.push(transaction.id); } } } const remainingQueue = queue.filter((t) => !processedTransactionIds.includes(t.id)); await this.saveQueue([...remainingQueue, ...updatedQueue]); if (processedTransactionIds.length > 0) { logger.info(`Cleared ${processedTransactionIds.length} processed transactions from queue`); } } catch (error) { logger.error("Queue processing failed:", error); } finally { this.isProcessing = false; } } async processQueuedTransaction(transaction) { try { await this.receiptService.createReceipt(transaction.data); } catch (error) { if (error?.message?.includes("duplicate key") || error?.message?.includes("already exists")) { logger.info("Transaction already processed (duplicate key), marking as successful:", { id: transaction.id, token: transaction.data.token, }); return; } throw new Error(`Failed to process queued transaction ${transaction.id}: ${error}`); } } async getQueueStatus() { if (this.isRender) { return { total: 0, pending: 0, failed: 0, oldestPending: null, isProcessing: false, disabled: true, message: "Offline queue disabled in Render environment", }; } try { const queue = await this.loadQueue(); const pending = queue.filter((t) => t.retryCount < t.maxRetries); const failed = queue.filter((t) => t.retryCount >= t.maxRetries); return { total: queue.length, pending: pending.length, failed: failed.length, oldestPending: pending.length > 0 ? pending[0].timestamp : null, isProcessing: this.isProcessing, }; } catch (error) { logger.error("Failed to get queue status:", error); return { total: 0, pending: 0, failed: 0, oldestPending: null, isProcessing: false, error: "Failed to read queue", }; } } async clearFailedTransactions() { try { const queue = await this.loadQueue(); const activeQueue = queue.filter((t) => t.retryCount < t.maxRetries); await this.saveQueue(activeQueue); logger.info("Failed transactions cleared from queue"); } catch (error) { logger.error("Failed to clear failed transactions:", error); throw error; } } async clearAllTransactions() { try { await this.saveQueue([]); logger.info("All transactions cleared from queue"); } catch (error) { logger.error("Failed to clear all transactions:", error); throw error; } } async loadQueue() { try { const data = await promises_1.default.readFile(this.queueFile, "utf-8"); return JSON.parse(data); } catch (error) { logger.warn("Failed to load queue, returning empty array:", error); return []; } } async saveQueue(queue) { try { await promises_1.default.writeFile(this.queueFile, JSON.stringify(queue, null, 2)); } catch (error) { logger.error("Failed to save queue:", error); throw error; } } generateId() { return `queue_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`; } startPeriodicSync() { node_cron_1.default.schedule("*/2 * * * *", () => { logger.debug("Starting periodic queue processing"); this.processQueue().catch((error) => { logger.error("Periodic queue processing failed:", error); }); }); logger.info("Periodic queue sync started (every 2 minutes)"); } } exports.OfflineQueueService = OfflineQueueService; //# sourceMappingURL=offline-queue.js.map