UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

320 lines 12.6 kB
/** * Cloud Synchronization Module * * Handles communication with Cloudflare Workers for: * - User verification * - Usage synchronization * - Subscription management * * Implements adaptive verification to minimize API calls */ import * as userManager from './userManager.js'; import { v4 as uuidv4 } from 'uuid'; // Import configurable endpoints (SECURITY: Remove hardcoded domains) import { getApiEndpoint } from '../config/endpoints.js'; // Configuration const DEFAULT_CONFIG = { apiEndpoint: getApiEndpoint('general'), // Configurable API endpoint (SECURITY FIX) apiKey: process.env.HIVE_API_KEY || '', // API key from environment variables (SECURITY FIX) syncInterval: 24 * 60 * 60 * 1000, // 24 hours in milliseconds (daily sync) verificationPercentage: { free: 100, // Free tier: verify 100% of the time (daily) basic: 50, // Basic tier: verify ~50% of the time professional: 20, // Pro tier: verify ~20% of the time enterprise: 10 // Enterprise: verify ~10% of the time }, lastVerified: null, userId: '', installationId: '' }; // Current configuration let config = { ...DEFAULT_CONFIG }; // Track verification interval let verificationInterval = null; /** * Initialize the cloud sync module */ export async function initialize(userId, installationId) { config.userId = userId; config.installationId = installationId; // Load last verification time from storage try { const user = await userManager.executeQuery('SELECT last_verified FROM users WHERE id = ?', [userId]); if (user && user.last_verified) { config.lastVerified = new Date(user.last_verified); } } catch (error) { console.error('[CLOUD-SYNC] Error loading last verification time:', error); } // Start verification timer startVerificationTimer(); } /** * Start the verification timer * This implements the adaptive verification approach */ export function startVerificationTimer() { // Clear any existing interval if (verificationInterval) { clearInterval(verificationInterval); } // Set up the interval to run daily verificationInterval = setInterval(async () => { // Check if verification should run based on subscription tier const shouldVerify = await shouldRunVerification(); if (shouldVerify) { await verifySubscription(); } }, config.syncInterval); console.log('[CLOUD-SYNC] Verification timer started'); } /** * Stop the verification timer */ export function stopVerificationTimer() { if (verificationInterval) { clearInterval(verificationInterval); verificationInterval = null; console.log('[CLOUD-SYNC] Verification timer stopped'); } } /** * Determine if verification should run based on subscription tier * This implements the adaptive verification approach */ export async function shouldRunVerification() { try { // Always verify if never verified before if (!config.lastVerified) { return true; } // Get user's subscription tier const user = await userManager.executeQuery('SELECT subscription_tier FROM users WHERE id = ?', [config.userId]); if (!user) { // Default to verifying if user not found return true; } // Get verification percentage for this tier const tierKey = user.subscription_tier; const percentage = config.verificationPercentage[tierKey] || 100; // Generate a random number between 0-100 const random = Math.floor(Math.random() * 100); // Verify if random number is less than the percentage return random < percentage; } catch (error) { console.error('[CLOUD-SYNC] Error determining if verification should run:', error); // Default to verifying in case of error return true; } } /** * Verify subscription status with Cloudflare Worker */ export async function verifySubscription(force = false) { try { // Skip if no user ID or installation ID if (!config.userId || !config.installationId) { console.log('[CLOUD-SYNC] Missing user ID or installation ID, skipping verification'); return { success: false, message: 'Missing user ID or installation ID' }; } // Get user data const user = await userManager.executeQuery('SELECT * FROM users WHERE id = ?', [config.userId]); if (!user) { console.log('[CLOUD-SYNC] User not found, skipping verification'); return { success: false, message: 'User not found' }; } // Prepare verification data const verificationData = { userId: config.userId, installationId: config.installationId, tier: user.subscription_tier, gumroadCustomerId: user.gumroad_customer_id, gumroadSubscriptionId: user.gumroad_subscription_id, gumroadProductId: user.gumroad_product_id, additionalCredits: user.additional_credits, // Keep legacy fields for backward compatibility lemonSqueezyCustomerId: user.lemon_squeezy_customer_id, lemonSqueezySubscriptionId: user.lemon_squeezy_subscription_id, timestamp: new Date().toISOString(), verificationId: uuidv4() }; // Sync usage data before verification await syncUsageData(); // Call Cloudflare Worker to verify subscription const url = `${config.apiEndpoint}/verify-subscription`; console.log(`[CLOUD-SYNC] Making verification request to: ${url}`); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify(verificationData) }); if (!response.ok) { throw new Error(`Verification failed with status: ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { // Update user's subscription information await userManager.executeQuery(`UPDATE users SET subscription_tier = ?, gumroad_customer_id = COALESCE(?, gumroad_customer_id), gumroad_subscription_id = COALESCE(?, gumroad_subscription_id), gumroad_product_id = COALESCE(?, gumroad_product_id), additional_credits = COALESCE(?, additional_credits), lemon_squeezy_customer_id = COALESCE(?, lemon_squeezy_customer_id), lemon_squeezy_subscription_id = COALESCE(?, lemon_squeezy_subscription_id), last_verified = CURRENT_TIMESTAMP WHERE id = ?`, [responseData.subscription.tier, responseData.subscription.gumroadCustomerId, responseData.subscription.gumroadSubscriptionId, responseData.subscription.gumroadProductId, responseData.subscription.additionalCredits, responseData.subscription.lemonSqueezyCustomerId, responseData.subscription.lemonSqueezySubscriptionId, config.userId]); // Update last verified time config.lastVerified = new Date(); await userManager.executeQuery('UPDATE users SET last_verified = ? WHERE id = ?', [config.lastVerified.toISOString(), config.userId]); console.log('[CLOUD-SYNC] Subscription verified successfully'); return { success: true, data: responseData }; } else { console.error('[CLOUD-SYNC] Verification failed:', responseData); return { success: false, message: responseData.message || 'Verification failed' }; } } catch (error) { const err = error; console.error('[CLOUD-SYNC] Error verifying subscription:', err); return { success: false, message: err.message || 'Unknown error' }; } } /** * Synchronize usage data with Cloudflare Worker */ export async function syncUsageData() { try { // Skip if no user ID if (!config.userId) { console.log('[CLOUD-SYNC] Missing user ID, skipping sync'); return { success: false, message: 'Missing user ID' }; } // Get unsynchronized usage data const lastSync = config.lastVerified || new Date(0); const usageData = await userManager.getUnsyncedUsageData(config.userId, lastSync); // Skip if no data to sync if (usageData.length === 0) { console.log('[CLOUD-SYNC] No usage data to sync'); return { success: true, message: 'No data to sync' }; } // Prepare sync data const syncData = { userId: config.userId, timestamp: new Date().toISOString(), syncId: uuidv4(), usageData }; // Call Cloudflare Worker to sync usage data const url = `${config.apiEndpoint}/sync-usage`; console.log(`[CLOUD-SYNC] Making sync request to: ${url}`); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify(syncData) }); if (!response.ok) { console.error(`[CLOUD-SYNC] Sync failed with status: ${response.status}`); return { success: false, message: `Sync failed with status: ${response.status}` }; } const responseData = await response.json(); if (responseData && responseData.success) { // Mark data as synchronized await userManager.markUsageAsSynced(config.userId, usageData); console.log(`[CLOUD-SYNC] Synced ${usageData.length} usage records successfully`); return { success: true, data: responseData }; } else { console.error('[CLOUD-SYNC] Sync failed:', responseData); return { success: false, message: responseData.message || 'Sync failed' }; } } catch (error) { const err = error; console.error('[CLOUD-SYNC] Error syncing usage data:', err); return { success: false, message: err.message || 'Unknown error' }; } } /** * Generate a checkout URL for subscription */ export async function getCheckoutUrl(tier) { try { // Skip if no user ID if (!config.userId) { throw new Error('Missing user ID'); } // Get user data const user = await userManager.executeQuery('SELECT * FROM users WHERE id = ?', [config.userId]); if (!user) { throw new Error('User not found'); } // Call Cloudflare Worker to get checkout URL const url = `${config.apiEndpoint}/create-checkout`; console.log(`[CLOUD-SYNC] Making checkout request to: ${url}`); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify({ userId: config.userId, email: user.email, name: user.name, tier }) }); if (!response.ok) { throw new Error(`Checkout URL creation failed with status: ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success && responseData.checkoutUrl) { return responseData.checkoutUrl; } else { throw new Error(responseData.message || 'Failed to create checkout URL'); } } catch (error) { const err = error; console.error('[CLOUD-SYNC] Error generating checkout URL:', err); throw err; } } /** * Force an immediate verification and sync * This is used when the user manually requests verification */ export async function forceVerification() { return await verifySubscription(true); } /** * Set the API endpoint for the Cloudflare Worker */ export function setApiEndpoint(endpoint) { config.apiEndpoint = endpoint; } /** * Get the current configuration */ export function getConfig() { return { ...config }; } //# sourceMappingURL=cloudSync.js.map