@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
JavaScript
/**
* 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