@receeco/pos-agent
Version:
Receeco POS Integration Middleware Agent
234 lines • 8.9 kB
JavaScript
;
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