File size: 3,552 Bytes
6111b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { isCloudEnabled } from "@/lib/localDb";
import { getRuntimePorts } from "@/lib/runtime/ports";

const { dashboardPort } = getRuntimePorts();

const INTERNAL_BASE_URL =
  process.env.BASE_URL ||
  process.env.NEXT_PUBLIC_BASE_URL ||
  process.env.NEXT_PUBLIC_APP_URL ||
  `http://localhost:${dashboardPort}`;

/**

 * Cloud sync scheduler

 */
export class CloudSyncScheduler {
  machineId: string | null;
  intervalMinutes: number;
  intervalId: ReturnType<typeof setInterval> | null;

  constructor(machineId: string | null = null, intervalMinutes = 15) {
    this.machineId = machineId;
    this.intervalMinutes = intervalMinutes;
    this.intervalId = null;
  }

  /**

   * Initialize machine ID if not provided

   */
  async initializeMachineId() {
    if (!this.machineId) {
      this.machineId = await getConsistentMachineId();
    }
  }

  /**

   * Start periodic sync (delays first sync to allow server to be ready)

   */
  async start() {
    if (this.intervalId) {
      return;
    }

    await this.initializeMachineId();

    // Delay first sync by 30 seconds to ensure server is ready
    const startupTimer = setTimeout(() => {
      this.syncWithRetry().catch(() => {});
    }, 30000);
    startupTimer.unref?.();

    // Then sync periodically
    this.intervalId = setInterval(
      () => {
        this.syncWithRetry().catch(() => {});
      },
      this.intervalMinutes * 60 * 1000
    );
    this.intervalId.unref?.();
  }

  /**

   * Stop periodic sync

   */
  stop() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  /**

   * Sync with retry logic (exponential backoff)

   */
  async syncWithRetry(maxRetries = 1) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const result = await this.sync();
        return result;
      } catch (error) {
        if (attempt === maxRetries) {
          return null;
        }

        const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Max 10s
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  /**

   * Perform sync via internal API route (handles token update to db.json)

   */
  async sync() {
    // Check if cloud is enabled
    const enabled = await isCloudEnabled();
    if (!enabled) {
      return null;
    }

    await this.initializeMachineId();

    // Call internal API route which handles both sync and token update
    const response = await fetch(`${INTERNAL_BASE_URL}/api/sync/cloud`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ machineId: this.machineId, action: "sync" }),
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      throw new Error(errorData.error || "Sync failed");
    }

    const result = await response.json();
    return result;
  }

  /**

   * Check if scheduler is running

   */
  isRunning() {
    return this.intervalId !== null;
  }
}

// Export a singleton instance if needed
let cloudSyncScheduler: CloudSyncScheduler | null = null;

export async function getCloudSyncScheduler(machineId: string | null = null, intervalMinutes = 15) {
  if (!cloudSyncScheduler) {
    cloudSyncScheduler = new CloudSyncScheduler(machineId, intervalMinutes);
  }
  return cloudSyncScheduler;
}