shinroute / src /lib /cloudSync.ts
shinmentakezo07
Add OmniRoute codebase without binary assets
9e4583c
Raw
History Blame Contribute Delete
3.83 kB
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);
}
}
/**
* Sync data to Cloud (shared utility)
* @param {string} machineId
* @param {string|null} createdKey - Key created during enable
*/
export async function syncToCloud(machineId, createdKey = null) {
if (!CLOUD_URL) {
return { error: "NEXT_PUBLIC_CLOUD_URL is not configured" };
}
// Get current data from db
const providers = await getProviderConnections();
const modelAliases = await getModelAliases();
const combos = await getCombos();
const apiKeys = await getApiKeys();
let response;
try {
// Send to Cloud
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();
// Update local db with tokens from Cloud (providers stored by ID)
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;
}
/**
* Update local db with data from Cloud
* Simple logic: if Cloud is newer, sync entire provider
* cloudProviders is object keyed by provider ID
*/
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();
// Simple logic: if Cloud is newer, sync entire provider
if (cloudUpdatedAt > localUpdatedAt) {
const updates = {
// Tokens
accessToken: cloudProvider.accessToken,
refreshToken: cloudProvider.refreshToken,
expiresAt: cloudProvider.expiresAt,
expiresIn: cloudProvider.expiresIn,
// Provider specific data
providerSpecificData:
cloudProvider.providerSpecificData || localProvider.providerSpecificData,
// Status fields
testStatus: cloudProvider.status || "active",
lastError: cloudProvider.lastError,
lastErrorAt: cloudProvider.lastErrorAt,
errorCode: cloudProvider.errorCode,
rateLimitedUntil: cloudProvider.rateLimitedUntil,
// Metadata
updatedAt: cloudProvider.updatedAt,
};
await updateProviderConnection(localProvider.id, updates);
}
}
}
export { CLOUD_URL, CLOUD_SYNC_TIMEOUT_MS };