| import { |
| getProviderConnections, |
| getModelAliases, |
| getCombos, |
| getApiKeys, |
| updateProviderConnection, |
| } from "@/lib/localDb"; |
|
|
| const CLOUD_URL = process.env.CLOUD_URL || process.env.NEXT_PUBLIC_CLOUD_URL; |
| const CLOUD_SYNC_TIMEOUT_MS = Number(process.env.CLOUD_SYNC_TIMEOUT_MS || 12000); |
|
|
| export async function fetchWithTimeout(url, options = {}, timeoutMs = CLOUD_SYNC_TIMEOUT_MS) { |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), timeoutMs); |
| try { |
| return await fetch(url, { ...options, signal: controller.signal }); |
| } finally { |
| clearTimeout(timeoutId); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export async function syncToCloud(machineId, createdKey = null) { |
| if (!CLOUD_URL) { |
| return { error: "NEXT_PUBLIC_CLOUD_URL is not configured" }; |
| } |
|
|
| |
| const providers = await getProviderConnections(); |
| const modelAliases = await getModelAliases(); |
| const combos = await getCombos(); |
| const apiKeys = await getApiKeys(); |
|
|
| let response; |
| try { |
| |
| response = await fetchWithTimeout(`${CLOUD_URL}/sync/${machineId}`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ |
| providers, |
| modelAliases, |
| combos, |
| apiKeys, |
| }), |
| }); |
| } catch (error) { |
| const isTimeout = error?.name === "AbortError"; |
| return { error: isTimeout ? "Cloud sync timeout" : "Cloud sync request failed" }; |
| } |
|
|
| if (!response.ok) { |
| const errorText = await response.text(); |
| const truncated = errorText.length > 200 ? errorText.slice(0, 200) + "…" : errorText; |
| console.log(`Cloud sync failed (${response.status}):`, truncated); |
| return { error: "Cloud sync failed" }; |
| } |
|
|
| const result = await response.json(); |
|
|
| |
| if (result.data && result.data.providers) { |
| await updateLocalTokens(result.data.providers); |
| } |
|
|
| const responseData: any = { |
| success: true, |
| message: "Synced successfully", |
| changes: result.changes, |
| }; |
|
|
| if (createdKey) { |
| responseData.createdKey = createdKey; |
| } |
|
|
| return responseData; |
| } |
|
|
| |
| |
| |
| |
| |
| async function updateLocalTokens(cloudProviders) { |
| const localProviders = await getProviderConnections(); |
|
|
| for (const localProvider of localProviders) { |
| const cloudProvider = cloudProviders[localProvider.id]; |
| if (!cloudProvider) continue; |
|
|
| const cloudUpdatedAt = new Date(cloudProvider.updatedAt || 0).getTime(); |
| const localUpdatedAt = new Date(localProvider.updatedAt || 0).getTime(); |
|
|
| |
| if (cloudUpdatedAt > localUpdatedAt) { |
| const updates = { |
| |
| accessToken: cloudProvider.accessToken, |
| refreshToken: cloudProvider.refreshToken, |
| expiresAt: cloudProvider.expiresAt, |
| expiresIn: cloudProvider.expiresIn, |
|
|
| |
| providerSpecificData: |
| cloudProvider.providerSpecificData || localProvider.providerSpecificData, |
|
|
| |
| testStatus: cloudProvider.status || "active", |
| lastError: cloudProvider.lastError, |
| lastErrorAt: cloudProvider.lastErrorAt, |
| errorCode: cloudProvider.errorCode, |
| rateLimitedUntil: cloudProvider.rateLimitedUntil, |
|
|
| |
| updatedAt: cloudProvider.updatedAt, |
| }; |
|
|
| await updateProviderConnection(localProvider.id, updates); |
| } |
| } |
| } |
|
|
| export { CLOUD_URL, CLOUD_SYNC_TIMEOUT_MS }; |
|
|