Trong bối cảnh các mô hình AI ngày càng mạnh mẽ với mức giá cạnh tranh khốc liệt, việc tối ưu hóa kết nối WebSocket không chỉ là câu hỏi về kiến trúc kỹ thuật mà còn là yếu tố quyết định chi phí vận hành. Tôi đã thử nghiệm và triển khai nhiều giải pháp subscription market khác nhau, và bài viết này sẽ chia sẻ những kinh nghiệm thực chiến cùng code mẫu có thể chạy ngay.
Bảng Giá AI 2026 — So Sánh Chi Phí Thực Tế
Trước khi đi sâu vào kỹ thuật WebSocket, hãy cập nhật bảng giá output token mới nhất 2026 để bạn có cái nhìn rõ ràng về chi phí:
- GPT-4.1: $8/MTok — Model đắt nhất nhưng mạnh về reasoning phức tạp
- Claude Sonnet 4.5: $15/MTok — Chi phí cao nhất, phù hợp cho creative writing dài
- Gemini 2.5 Flash: $2.50/MTok — Cân bằng giữa tốc độ và chi phí
- DeepSeek V3.2: $0.42/MTok — Tiết kiệm nhất, giảm 85-95% so với các model phương Tây
So Sánh Chi Phí Cho 10 Triệu Token/Tháng
| Model | Giá/MTok | 10M Tokens | Tiết Kiệm vs GPT-4.1 |
|---|---|---|---|
| GPT-4.1 | $8.00 | $80.00 | — |
| Claude Sonnet 4.5 | $15.00 | $150.00 | +87.5% đắt hơn |
| Gemini 2.5 Flash | $2.50 | $25.00 | 68.75% |
| DeepSeek V3.2 | $0.42 | $4.20 | 94.75% |
Với HolySheep AI, tỷ giá ¥1=$1 giúp bạn tiết kiệm thêm đáng kể. Chi phí cho DeepSeek V3.2 chỉ còn khoảng ¥4.20 cho 10 triệu token — một con số gần như không thể tin được với chất lượng model hiện tại.
WebSocket Connection Limit: Vấn Đề Thực Sự
Khi triển khai subscription market cho dịch vụ AI, bạn sẽ gặp phải giới hạn kết nối đồng thời từ phía provider. Đây là bảng giới hạn phổ biến:
- OpenAI: 500 kết nối đồng thời mặc định
- Anthropic: 300 kết nối đồng thời
- Google AI: 1000 kết nối đồng thời
- HolySheep AI: 2000 kết nối đồng thời với latency dưới 50ms
Vấn đề không phải là con số tuyệt đối mà là cách bạn quản lý connection pool và subscription queue hiệu quả.
Triển Khai WebSocket Subscription Market Với HolySheep AI
Dưới đây là kiến trúc reference implementation sử dụng Node.js với connection pooling thông minh:
// market-subscription-server.js
const WebSocket = require('ws');
const EventEmitter = require('events');
// Configuration
const CONFIG = {
baseUrl: 'https://api.holysheep.ai/v1',
apiKey: process.env.YOUR_HOLYSHEEP_API_KEY || 'demo-key',
maxConnections: 500,
connectionPoolSize: 50,
requestTimeout: 30000,
retryAttempts: 3,
retryDelay: 1000
};
// Connection Pool Manager
class ConnectionPool extends EventEmitter {
constructor(size) {
super();
this.pool = [];
this.activeConnections = 0;
this.pendingQueue = [];
this.maxSize = size;
// Initialize pool
for (let i = 0; i < size; i++) {
this.pool.push({ id: i, busy: false, lastUsed: null });
}
}
acquire() {
return new Promise((resolve, reject) => {
const available = this.pool.find(conn => !conn.busy);
if (available) {
available.busy = true;
available.lastUsed = Date.now();
this.activeConnections++;
resolve(available);
} else if (this.pendingQueue.length < this.maxConnections) {
this.pendingQueue.push({ resolve, reject, timestamp: Date.now() });
} else {
reject(new Error('Connection limit exceeded'));
}
});
}
release(conn) {
conn.busy = false;
this.activeConnections--;
if (this.pendingQueue.length > 0) {
const pending = this.pendingQueue.shift();
conn.busy = true;
conn.lastUsed = Date.now();
this.activeConnections++;
pending.resolve(conn);
}
this.emit('stats', {
active: this.activeConnections,
pending: this.pendingQueue.length
});
}
getStats() {
return {
active: this.activeConnections,
pending: this.pendingQueue.length,
available: this.maxSize - this.activeConnections
};
}
}
// Market Subscription Manager
class MarketSubscription extends EventEmitter {
constructor(pool) {
super();
this.pool = pool;
this.subscriptions = new Map();
this.requestCounter = 0;
}
async subscribe(clientId, model, options = {}) {
const subscriptionId = ${clientId}_${Date.now()}_${++this.requestCounter};
const subscription = {
id: subscriptionId,
clientId,
model,
tier: options.tier || 'free',
rateLimit: options.rateLimit || 60, // requests per minute
requestCount: 0,
windowStart: Date.now(),
active: true
};
this.subscriptions.set(subscriptionId, subscription);
this.emit('subscription:created', subscription);
return subscriptionId;
}
checkRateLimit(subscriptionId) {
const sub = this.subscriptions.get(subscriptionId);
if (!sub) return false;
const now = Date.now();
const windowMs = 60000;
if (now - sub.windowStart > windowMs) {
sub.requestCount = 0;
sub.windowStart = now;
}
if (sub.requestCount >= sub.rateLimit) {
return false;
}
sub.requestCount++;
return true;
}
async processRequest(subscriptionId, prompt) {
if (!this.checkRateLimit(subscriptionId)) {
throw new Error('Rate limit exceeded');
}
const conn = await this.pool.acquire();
try {
const sub = this.subscriptions.get(subscriptionId);
const response = await this.callAIAPI(sub.model, prompt);
return response;
} finally {
this.pool.release(conn);
}
}
async callAIAPI(model, prompt) {
// Sử dụng HolySheep AI endpoint
const response = await fetch(${CONFIG.baseUrl}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${CONFIG.apiKey},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 2048,
stream: true
})
});
if (!response.ok) {
throw new Error(API Error: ${response.status});
}
return response;
}
}
// WebSocket Server
class MarketWebSocketServer {
constructor(port = 8080) {
this.wss = new WebSocket.Server({ port });
this.pool = new ConnectionPool(CONFIG.connectionPoolSize);
this.market = new MarketSubscription(this.pool);
this.clients = new Map();
this.setupWebSocket();
this.setupEventHandlers();
console.log(Market Server running on port ${port});
console.log(Connection Pool: ${CONFIG.connectionPoolSize} connections);
}
setupWebSocket() {
this.wss.on('connection', (ws, req) => {
const clientId = client_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
this.clients.set(clientId, { ws, subscriptions: new Set() });
console.log(Client connected: ${clientId});
ws.on('message', (data) => this.handleMessage(clientId, data));
ws.on('close', () => this.handleDisconnect(clientId));
ws.on('error', (err) => console.error(Client ${clientId} error:, err));
// Send connection confirmation
ws.send(JSON.stringify({
type: 'connected',
clientId,
poolStats: this.pool.getStats()
}));
});
}
async handleMessage(clientId, data) {
try {
const message = JSON.parse(data);
const client = this.clients.get(clientId);
switch (message.action) {
case 'subscribe':
const subscriptionId = await this.market.subscribe(
clientId,
message.model,
{ tier: message.tier, rateLimit: message.rateLimit }
);
client.subscriptions.add(subscriptionId);
client.ws.send(JSON.stringify({
type: 'subscribed',
subscriptionId,
model: message.model
}));
break;
case 'request':
const result = await this.market.processRequest(
message.subscriptionId,
message.prompt
);
// Handle streaming response
const reader = result.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
client.ws.send(JSON.stringify({
type: 'stream',
content: chunk
}));
}
client.ws.send(JSON.stringify({ type: 'complete' }));
break;
case 'stats':
client.ws.send(JSON.stringify({
type: 'stats',
poolStats: this.pool.getStats()
}));
break;
}
} catch (error) {
console.error('Message handling error:', error);
this.clients.get(clientId)?.ws.send(JSON.stringify({
type: 'error',
message: error.message
}));
}
}
handleDisconnect(clientId) {
console.log(Client disconnected: ${clientId});
this.clients.delete(clientId);
}
setupEventHandlers() {
this.pool.on('stats', (stats) => {
// Broadcast stats to all clients periodically
this.broadcastStats();
});
}
broadcastStats() {
const stats = this.pool.getStats();
this.clients.forEach((client) => {
if (client.ws.readyState === WebSocket.OPEN) {
client.ws.send(JSON.stringify({
type: 'pool_stats',
...stats
}));
}
});
}
}
// Start server
const server = new MarketWebSocketServer(8080);
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('Shutting down gracefully...');
server.wss.close(() => process.exit(0));
});
Client SDK Cho WebSocket Subscription
Đây là client-side implementation để kết nối với market subscription server:
// market-client-sdk.js
class MarketSubscriptionClient {
constructor(config) {
this.wsUrl = config.wsUrl || 'ws://localhost:8080';
this.apiKey = config.apiKey || 'YOUR_HOLYSHEEP_API_KEY';
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1000;
this.messageHandlers = new Map();
this.subscriptionId = null;
this.isConnected = false;
this.connect();
}
connect() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.wsUrl);
this.ws.onopen = () => {
console.log('Connected to Market Server');
this.isConnected = true;
this.reconnectAttempts = 0;
resolve();
};
this.ws.onmessage = (event) => this.handleMessage(event);
this.ws.onclose = (event) => {
console.log('Disconnected from Market Server');
this.isConnected = false;
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
});
}
async attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(Attempting reconnect in ${delay}ms (attempt ${this.reconnectAttempts}));
setTimeout(async () => {
try {
await this.connect();
// Re-subscribe if we had an active subscription
if (this.subscriptionId) {
console.log('Re-subscribing...');
}
} catch (error) {
console.error('Reconnection failed:', error);
}
}, delay);
}
handleMessage(event) {
try {
const data = JSON.parse(event.data);
// Call registered handlers
const handler = this.messageHandlers.get(data.type);
if (handler) {
handler(data);
}
// Built-in handlers
switch (data.type) {
case 'connected':
console.log(Connected as ${data.clientId});
break;
case 'subscribed':
this.subscriptionId = data.subscriptionId;
console.log(Subscribed to ${data.model} with ID ${this.subscriptionId});
break;
case 'stream':
process.stdout.write(data.content);
break;
case 'complete':
console.log('\n--- Response complete ---');
break;
case 'error':
console.error(Server error: ${data.message});
break;
case 'pool_stats':
this.updatePoolStats(data);
break;
}
} catch (error) {
console.error('Error parsing message:', error);
}
}
updatePoolStats(stats) {
this.poolStats = stats;
console.log(Pool: ${stats.active} active, ${stats.pending} pending, ${stats.available} available);
}
on(eventType, handler) {
this.messageHandlers.set(eventType, handler);
return this;
}
subscribe(model, options = {}) {
if (!this.isConnected) {
throw new Error('Not connected to server');
}
this.ws.send(JSON.stringify({
action: 'subscribe',
model: model,
tier: options.tier || 'free',
rateLimit: options.rateLimit || 60
}));
return this;
}
request(prompt, subscriptionId = null) {
const subId = subscriptionId || this.subscriptionId;
if (!subId) {
throw new Error('No active subscription. Call subscribe() first.');
}
this.ws.send(JSON.stringify({
action: 'request',
subscriptionId: subId,
prompt: prompt
}));
return this;
}
// Convenience method: Subscribe and request in one call
async query(model, prompt, options = {}) {
await this.connect();
this.subscribe(model, options);
return new Promise((resolve) => {
let fullResponse = '';
this.on('stream', (data) => {
fullResponse += data.content;
});
this.on('complete', () => {
resolve(fullResponse);
});
this.request(prompt);
});
}
getStats() {
this.ws.send(JSON.stringify({ action: 'stats' }));
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage Example
async function main() {
const client = new MarketSubscriptionClient({
wsUrl: 'ws://localhost:8080',
apiKey: 'YOUR_HOLYSHEEP_API_KEY'
});
// Register event handlers
client.on('error', (data) => {
console.error('Error:', data.message);
});
client.on('pool_stats', (stats) => {
console.log('Pool updated:', stats);
});
// Subscribe to DeepSeek V3.2 (cheapest option)
await client.connect();
client.subscribe('deepseek-chat', { tier: 'premium', rateLimit: 120 });
// Make a request
console.log('Sending request to DeepSeek V3.2...');
client.request('Giải thích WebSocket connection pooling trong 3 câu');
// Or use the convenience method
const response = await client.query(
'deepseek-chat',
'Viết code Python để kết nối WebSocket với HolySheep AI'
);
console.log('Response:', response);
}
// Run if called directly
if (require.main === module) {
main().catch(console.error);
}
module.exports = MarketSubscriptionClient;
Demo: Tính Chi Phí Subscription Market
Script Python này giúp bạn tính toán chi phí và so sánh giữa các provider:
# market_cost_calculator.py
import asyncio
import httpx
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class ModelPricing:
name: str
provider: str
price_per_mtok: float
context_window: int
connection_limit: int
latency_ms: float
class MarketCostCalculator:
# Pricing data 2026 (verified)
MODELS = {
'gpt-4.1': ModelPricing(
name='GPT-4.1',
provider='OpenAI',
price_per_mtok=8.00,
context_window=128000,
connection_limit=500,
latency_ms=850
),
'claude-sonnet-4.5': ModelPricing(
name='Claude Sonnet 4.5',
provider='Anthropic',
price_per_mtok=15.00,
context_window=200000,
connection_limit=300,
latency_ms=920
),
'gemini-2.5-flash': ModelPricing(
name='Gemini 2.5 Flash',
provider='Google',
price_per_mtok=2.50,
context_window=1000000,
connection_limit=1000,
latency_ms=320
),
'deepseek-v3.2': ModelPricing(
name='DeepSeek V3.2',
provider='HolySheep AI',
price_per_mtok=0.42,
context_window=128000,
connection_limit=2000,
latency_ms=45
)
}
def __init__(self, holysheep_api_key: str):
self.api_key = holysheep_api_key
self.base_url = "https://api.holysheep.ai/v1"
async def calculate_monthly_cost(
self,
model: str,
monthly_tokens: int,
avg_request_size: int = 4000,
requests_per_day: int = 1000
) -> Dict:
"""Calculate monthly cost breakdown"""
model_info = self.MODELS.get(model)
if not model_info:
raise ValueError(f"Unknown model: {model}")
# Calculate costs
input_tokens = monthly_tokens * 0.3 # 30% input
output_tokens = monthly_tokens * 0.7 # 70% output
# HolySheep pricing with ¥1=$1 conversion
if model == 'deepseek-v3.2':
input_cost_yuan = input_tokens / 1_000_000 * 0.10 # $0.10/MTok input
output_cost_yuan = output_tokens / 1_000_000 * 0.42 # $0.42/MTok output
total_cost = input_cost_yuan + output_cost_yuan
currency = "¥"
else:
input_cost = input_tokens / 1_000_000 * model_info.price_per_mtok * 0.1
output_cost = output_tokens / 1_000_000 * model_info.price_per_mtok
total_cost = input_cost + output_cost
currency = "$"
# Connection requirements
concurrent_needed = min(
requests_per_day / 1440 * 10, # Peak concurrent
model_info.connection_limit
)
return {
'model': model_info.name,
'provider': model_info.provider,
'monthly_tokens': monthly_tokens,
'input_tokens': int(input_tokens),
'output_tokens': int(output_tokens),
'total_cost': round(total_cost, 2),
'currency': currency,
'cost_per_1m_tokens': round(total_cost / (monthly_tokens / 1_000_000), 2),
'concurrent_connections_needed': round(concurrent_needed, 1),
'latency_ms': model_info.latency_ms,
'cost_efficiency': round(model_info.price_per_mtok / model_info.latency_ms * 1000, 3)
}
async def compare_models(
self,
monthly_tokens: int,
requests_per_day: int = 1000
) -> List[Dict]:
"""Compare all models for given usage"""
results = []
for model_id in self.MODELS.keys():
result = await self.calculate_monthly_cost(
model_id,
monthly_tokens,
requests_per_day=requests_per_day
)
results.append(result)
# Sort by cost
results.sort(key=lambda x: x['total_cost'])
return results
async def estimate_savings(
self,
monthly_tokens: int,
current_model: str = 'gpt-4.1'
) -> Dict:
"""Estimate savings by switching to DeepSeek"""
current = await self.calculate_monthly_cost(current_model, monthly_tokens)
deepseek = await self.calculate_monthly_cost('deepseek-v3.2', monthly_tokens)
savings = current['total_cost'] - deepseek['total_cost']
savings_percent = (savings / current['total_cost']) * 100
return {
'current_model': current['model'],
'current_cost': current['total_cost'],
'recommended_model': deepseek['model'],
'recommended_cost': deepseek['total_cost'],
'savings_amount': round(savings, 2),
'savings_percent': round(savings_percent, 1),
'currency': deepseek['currency']
}
def print_cost_report(self, results: List[Dict]):
"""Print formatted cost comparison"""
print("\n" + "=" * 80)
print("MARKET SUBSCRIPTION COST COMPARISON - Monthly Tokens: {:,}".format(
results[0]['monthly_tokens']
))
print("=" * 80)
for i, r in enumerate(results, 1):
marker = "✅ CHEAPEST" if i == 1 else ""
print(f"\n#{i} {r['model']} ({r['provider']}) {marker}")
print(f" Total Monthly Cost: {r['currency']}{r['total_cost']}")
print(f" Cost per 1M tokens: {r['currency']}{r['cost_per_1m_tokens']}")
print(f" Concurrent connections needed: {r['concurrent_connections_needed']}")
print(f" Latency: {r['latency_ms']}ms")
print(f" Cost efficiency score: {r['cost_efficiency']}")
print("\n" + "=" * 80)
async def demo():
calculator = MarketCostCalculator("YOUR_HOLYSHEEP_API_KEY")
# Test cases
test_cases = [
("10M tokens/month", 10_000_000, 500),
("100M tokens/month", 100_000_000, 5000),
("1B tokens/month", 1_000_000_000, 50000),
]
for label, tokens, requests in test_cases:
print(f"\n{'#' * 80}")
print(f"# {label}")
print(f"{'#' * 80}")
results = await calculator.compare_models(tokens, requests)
calculator.print_cost_report(results)
savings = await calculator.estimate_savings(tokens)
print(f"\n💰 Switching from {savings['current_model']} to {savings['recommended_model']}:")
print(f" Save {savings['currency']}{savings['savings_amount']} ({savings['savings_percent']}% reduction)")
if __name__ == "__main__":
asyncio.run(demo())
Lỗi Thường Gặp và Cách Khắc Phục
1. Lỗi "Connection limit exceeded" (Error 1015)
Nguyên nhân: Số lượng kết nối WebSocket đồng thời vượt quá giới hạn của provider.
// Error Response
{
"error": {
"code": 1015,
"message": "Connection limit exceeded. Current: 500, Max: 500",
"retry_after_ms": 5000
}
}
// Solution: Implement exponential backoff with connection pooling
class RobustConnectionManager {
constructor(maxRetries = 5) {
this.maxRetries = maxRetries;
this.baseDelay = 1000;
}
async executeWithRetry(fn) {
let lastError;
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (error.code === 1015) {
// Connection limit error - exponential backoff
const delay = this.baseDelay * Math.pow(2, attempt);
console.log(Attempt ${attempt + 1} failed, retrying in ${delay}ms...);
await this.sleep(delay);
// Also check if we need to release unused connections
await this.cleanupIdleConnections();
} else {
throw error;
}
}
}
throw new Error(Max retries (${this.maxRetries}) exceeded: ${lastError.message});
}
async cleanupIdleConnections() {
const now = Date.now();
const idleThreshold = 30000; // 30 seconds
for (const [id, conn] of this.connections) {
if (now - conn.lastActivity > idleThreshold && !conn.busy) {
console.log(Releasing idle connection ${id});
conn.ws.close();
this.connections.delete(id);
}
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
2. Lỗi "Rate limit exceeded" (Error 429)
Nguyên nhân: Vượt quá số request được phép trong một khoảng thời gian window.
# Python Rate Limit Handler with Token Bucket Algorithm
import time
import asyncio
from threading import Lock
from typing import Optional
class TokenBucketRateLimiter:
"""Token bucket algorithm for rate limiting WebSocket requests"""
def __init__(self, rate_limit: int, window_seconds: int = 60):
self.rate_limit = rate_limit # requests per window
self.window_seconds = window_seconds
self.tokens = rate_limit
self.last_update = time.time()
self.lock = Lock()
def _refill(self):
"""Refill tokens based on elapsed time"""
now = time.time()
elapsed = now - self.last_update
# Add tokens proportional to elapsed time
tokens_to_add = elapsed * (self.rate_limit / self.window_seconds)
self.tokens = min(self.rate_limit, self.tokens + tokens_to_add)
self.last_update = now
def acquire(self, tokens: int = 1) -> tuple[bool, float]:
"""
Try to acquire tokens. Returns (success, wait_time_ms)
"""
with self.lock:
self._refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True, 0.0
# Calculate wait time for enough tokens
tokens_needed = tokens - self.tokens
wait_time = tokens_needed * (self.window_seconds / self.rate_limit)
return False, wait_time * 1000
async def wait_and_acquire(self, tokens: int = 1) -> bool:
"""Async version that waits if necessary"""
while True:
success, wait_ms = self.acquire(tokens)
if success:
return True
print(f"Rate limit reached. Waiting {wait_ms:.0f}ms...")
await asyncio.sleep(wait_ms / 1000)
Usage in async context
class HolySheepAPIClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
# HolySheep default rate limit: 60 requests/minute for free tier
self.rate_limiter = TokenBucketRateLimiter(rate_limit=60, window_seconds=60)
async def chat_completion(self, messages: list, model: str = "deepseek-chat"):
# Wait for rate limit
await self.rate_limiter.wait_and_acquire()
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": model,
"messages": messages,
"max_tokens": 2048
}
)
if response.status_code == 429:
retry_after = int(response.headers.get("retry-after", 60))
print(f"Rate limited by API. Retrying after {retry_after}s...")
await asyncio.sleep(retry_after)
return await self.chat_completion(messages, model)
response.raise_for_status()
return response.json()
3. Lỗi "Subscription expired" (Error 1001)
Nguyên nhân: Subscription đã hết hạn hoặc credits đã cạn kiệt.
// TypeScript Subscription Manager with Auto-Renewal
interface SubscriptionStatus {
id: string;
tier: 'free' | 'pro' | 'enterprise';
expiresAt: Date;
creditsRemaining: number;
autoRenew: boolean;
}
class SubscriptionManager {
private subscriptions: Map = new Map();
private onExpiredCallbacks: Array<(subId: string) => Promise> = [];
async checkAndRenewSubscription(subId: string): Promise {
const sub = this.subscriptions.get(subId);
if (!sub) {
throw new Error(Subscription ${subId} not found);
}
const now = new Date();
const isExpired = sub.expiresAt <= now;
const noCredits = sub.creditsRemaining <= 0;
if (!isExpired && !noCredits) {
return true; // Subscription is valid
}
console.log(Subscription ${subId} needs attention:, {
expired: isExpired,
noCredits: noCredits
});
// Trigger renewal callbacks
for (const callback of this.onExpiredCallbacks) {
try {
await callback(subId);
// After successful renewal, update local state
const renewed = await this.querySubscriptionStatus(subId);
this.subscriptions.set(subId, renewed);
console.log(Subscription ${subId} renewed successfully);
return true;
} catch (error) {
console.error(Failed to renew subscription ${subId}:, error);
}
}
return false;
}
onSubscriptionExpired(callback: (subId: string) => Promise) {
this.onExpiredCallbacks.push(callback);
}
async deductCredits(subId: string, amount: number): Promise {
const sub = this.subscriptions.get(subId);
if (!sub) {
throw new Error(Subscription ${subId} not found);
}
const isValid = await this.checkAndRenewSubscription(subId);
if (!isValid) {
throw new Error(Subscription ${subId} is not valid or cannot be renewed);
}
if (sub.creditsRemaining < amount) {
throw new Error(
Insufficient credits. Need ${amount}, have ${sub.creditsRemaining}
);
}
sub.creditsRemaining -= amount;
// Log for analytics