Spaces:
Paused
Paused
File size: 4,726 Bytes
35743bd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | import { getProviderConnections, updateProviderConnection } from "@/lib/localDb";
import { buildConfigSyncEnvelope, toLegacyCloudSyncPayload } from "@/lib/sync/bundle";
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);
type JsonRecord = Record<string, unknown>;
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function toStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value : null;
}
function toDateMs(value: unknown): number {
if (typeof value === "string" || typeof value === "number" || value instanceof Date) {
const parsed = new Date(value).getTime();
return Number.isFinite(parsed) ? parsed : 0;
}
return 0;
}
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" };
}
// Keep legacy field names for upstream compatibility, but derive them
// from a canonical sync bundle with deterministic version hashing.
const { version, bundle } = await buildConfigSyncEnvelope();
const legacyPayload = toLegacyCloudSyncPayload(bundle);
let response;
try {
// Send to Cloud
response = await fetchWithTimeout(`${CLOUD_URL}/sync/${machineId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...legacyPayload,
version,
}),
});
} 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,
version,
};
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: unknown) {
const cloudProvidersMap = asRecord(cloudProviders);
const localProviders = await getProviderConnections();
for (const localProviderRaw of localProviders as unknown[]) {
const localProvider = asRecord(localProviderRaw);
const localProviderId = toStringOrNull(localProvider.id);
if (!localProviderId) continue;
const cloudProvider = asRecord(cloudProvidersMap[localProviderId]);
if (Object.keys(cloudProvider).length === 0) continue;
const cloudUpdatedAt = toDateMs(cloudProvider.updatedAt);
const localUpdatedAt = toDateMs(localProvider.updatedAt);
// 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(localProviderId, updates);
}
}
}
export { CLOUD_URL, CLOUD_SYNC_TIMEOUT_MS };
|