leafrelay 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FBT2UYMY.js → chunk-3TTRDNHP.js} +123 -32
- package/dist/chunk-3TTRDNHP.js.map +1 -0
- package/dist/{chunk-QRJDW6FM.js → chunk-VXZVIMCG.js} +3 -3
- package/dist/chunk-VXZVIMCG.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/daemon.js +12 -5
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-FBT2UYMY.js.map +0 -1
- package/dist/chunk-QRJDW6FM.js.map +0 -1
|
@@ -2449,19 +2449,30 @@ var SyncEngine = class {
|
|
|
2449
2449
|
stopRemoteReconnect;
|
|
2450
2450
|
eventTimers = /* @__PURE__ */ new Map();
|
|
2451
2451
|
stopped = false;
|
|
2452
|
+
failedPaths = /* @__PURE__ */ new Map();
|
|
2453
|
+
retryTimer;
|
|
2454
|
+
retryDelayMs = 3e4;
|
|
2455
|
+
pendingRemoteVersion;
|
|
2456
|
+
pendingInitialization = false;
|
|
2457
|
+
get syncError() {
|
|
2458
|
+
if (this.failedPaths.size === 0) {
|
|
2459
|
+
return void 0;
|
|
2460
|
+
}
|
|
2461
|
+
const [path, failure] = this.failedPaths.entries().next().value;
|
|
2462
|
+
return `${this.failedPaths.size} path(s) could not synchronize, including ${path}: ${failure.message}. Other paths remain active; failed paths will retry automatically.`;
|
|
2463
|
+
}
|
|
2452
2464
|
async start() {
|
|
2453
2465
|
this.stopped = false;
|
|
2454
|
-
this.
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
await this.initializeState(currentVersion, localFiles, remoteFiles);
|
|
2461
|
-
} else {
|
|
2462
|
-
this.restoreConflicts();
|
|
2463
|
-
await this.startupReconcile(currentVersion, localFiles, remoteFiles);
|
|
2466
|
+
const startup = this.enqueue(() => this.initialize());
|
|
2467
|
+
try {
|
|
2468
|
+
await startup;
|
|
2469
|
+
} catch (error2) {
|
|
2470
|
+
await this.stop();
|
|
2471
|
+
throw error2;
|
|
2464
2472
|
}
|
|
2473
|
+
}
|
|
2474
|
+
async initialize() {
|
|
2475
|
+
this.state = await this.options.stateStore.load();
|
|
2465
2476
|
this.stopLocalWatch = await this.options.local.watch((path) => this.schedule(path, "local"));
|
|
2466
2477
|
this.stopRemoteChange = this.options.remote.onChange((change) => {
|
|
2467
2478
|
if (this.stopped) {
|
|
@@ -2480,10 +2491,24 @@ var SyncEngine = class {
|
|
|
2480
2491
|
this.options.log(`Failed to reconcile after reconnecting: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2481
2492
|
});
|
|
2482
2493
|
});
|
|
2483
|
-
|
|
2494
|
+
const currentVersion = await this.options.remote.getCurrentVersion();
|
|
2495
|
+
const localFiles = await this.options.local.listFiles();
|
|
2496
|
+
const remoteFiles = this.remoteFilePaths();
|
|
2497
|
+
this.options.log(`Checking replica at Overleaf version ${currentVersion}: ${localFiles.length} local file(s), ${remoteFiles.length} remote file(s).`);
|
|
2498
|
+
if (!this.state || !this.state.initialized) {
|
|
2499
|
+
await this.initializeState(currentVersion, localFiles, remoteFiles);
|
|
2500
|
+
} else {
|
|
2501
|
+
this.restoreConflicts();
|
|
2502
|
+
await this.startupReconcile(currentVersion, localFiles, remoteFiles);
|
|
2503
|
+
}
|
|
2504
|
+
this.options.log(this.syncError ? `Synchronization active with pending retries. ${this.syncError}` : `Synchronization active at Overleaf version ${this.state.remoteVersion}.`);
|
|
2484
2505
|
}
|
|
2485
2506
|
async stop() {
|
|
2486
2507
|
this.stopped = true;
|
|
2508
|
+
if (this.retryTimer) {
|
|
2509
|
+
clearTimeout(this.retryTimer);
|
|
2510
|
+
this.retryTimer = void 0;
|
|
2511
|
+
}
|
|
2487
2512
|
this.stopRemoteChange?.();
|
|
2488
2513
|
this.stopRemoteChange = void 0;
|
|
2489
2514
|
this.stopRemoteReconnect?.();
|
|
@@ -2557,21 +2582,25 @@ var SyncEngine = class {
|
|
|
2557
2582
|
await this.options.stateStore.save(this.state);
|
|
2558
2583
|
}
|
|
2559
2584
|
this.restoreConflicts();
|
|
2585
|
+
this.pendingRemoteVersion = currentVersion;
|
|
2586
|
+
this.pendingInitialization = true;
|
|
2560
2587
|
const paths = /* @__PURE__ */ new Set([...localFiles, ...remoteFiles, ...Object.keys(this.state.files)]);
|
|
2561
2588
|
for (const path of [...paths].sort()) {
|
|
2562
2589
|
const normalized = normalizePath2(path);
|
|
2563
2590
|
if (this.conflicts.has(normalized)) {
|
|
2564
2591
|
continue;
|
|
2565
2592
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2593
|
+
try {
|
|
2594
|
+
if (this.state.files[normalized] !== void 0) {
|
|
2595
|
+
await this.synchronizePath(normalized, "local");
|
|
2596
|
+
} else {
|
|
2597
|
+
await this.initializePath(normalized);
|
|
2598
|
+
}
|
|
2599
|
+
} catch (error2) {
|
|
2600
|
+
this.recordFailure(normalized, "local", error2);
|
|
2569
2601
|
}
|
|
2570
|
-
await this.initializePath(normalized);
|
|
2571
2602
|
}
|
|
2572
|
-
this.
|
|
2573
|
-
this.state.initialized = true;
|
|
2574
|
-
await this.options.stateStore.save(this.state);
|
|
2603
|
+
await this.finishRemoteBatch();
|
|
2575
2604
|
if (this.conflicts.size !== 0) {
|
|
2576
2605
|
this.options.log(`Synchronization remains active; ${this.conflicts.size} conflicting path(s) are paused.`);
|
|
2577
2606
|
}
|
|
@@ -2644,7 +2673,7 @@ var SyncEngine = class {
|
|
|
2644
2673
|
throw new Error("Synchronization paused: Overleaf history is unavailable, so remote changes cannot be distinguished safely.");
|
|
2645
2674
|
}
|
|
2646
2675
|
}
|
|
2647
|
-
const candidates = new Set(localChanged);
|
|
2676
|
+
const candidates = /* @__PURE__ */ new Set([...localChanged, ...this.failedPaths.keys()]);
|
|
2648
2677
|
const knownFiles = /* @__PURE__ */ new Set([...Object.keys(state.files), ...localFiles, ...remoteFiles]);
|
|
2649
2678
|
for (const path of [...localFiles, ...remoteFiles]) {
|
|
2650
2679
|
if (state.files[path] === void 0) {
|
|
@@ -2652,6 +2681,9 @@ var SyncEngine = class {
|
|
|
2652
2681
|
}
|
|
2653
2682
|
}
|
|
2654
2683
|
for (const change of remoteDiff.diff) {
|
|
2684
|
+
if (!change.operation) {
|
|
2685
|
+
continue;
|
|
2686
|
+
}
|
|
2655
2687
|
const paths = [change.pathname, change.newPathname].filter((path) => Boolean(path));
|
|
2656
2688
|
for (const changedPath of paths.map(normalizePath2)) {
|
|
2657
2689
|
let matched = false;
|
|
@@ -2666,21 +2698,19 @@ var SyncEngine = class {
|
|
|
2666
2698
|
}
|
|
2667
2699
|
}
|
|
2668
2700
|
}
|
|
2669
|
-
|
|
2701
|
+
this.pendingRemoteVersion = currentVersion;
|
|
2670
2702
|
this.options.log(`Reconciling ${candidates.size} changed path(s).`);
|
|
2671
2703
|
for (const path of [...candidates].sort()) {
|
|
2704
|
+
if (this.stopped) {
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2672
2707
|
try {
|
|
2673
|
-
await this.
|
|
2708
|
+
await this.synchronizePath(path, localChanged.has(path) ? "local" : "remote");
|
|
2674
2709
|
} catch (error2) {
|
|
2675
|
-
|
|
2676
|
-
this.options.log(`Failed to synchronize ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2710
|
+
this.recordFailure(path, localChanged.has(path) ? "local" : "remote", error2);
|
|
2677
2711
|
}
|
|
2678
2712
|
}
|
|
2679
|
-
|
|
2680
|
-
throw new Error("Synchronization paused for failed paths; successful paths were checkpointed.");
|
|
2681
|
-
}
|
|
2682
|
-
state.remoteVersion = currentVersion;
|
|
2683
|
-
await this.options.stateStore.save(state);
|
|
2713
|
+
await this.finishRemoteBatch();
|
|
2684
2714
|
if (this.conflicts.size !== 0) {
|
|
2685
2715
|
this.options.log(`Synchronization remains active; ${this.conflicts.size} conflicting path(s) are paused.`);
|
|
2686
2716
|
}
|
|
@@ -2695,7 +2725,7 @@ var SyncEngine = class {
|
|
|
2695
2725
|
await this.options.local.listFiles(),
|
|
2696
2726
|
this.remoteFilePaths()
|
|
2697
2727
|
);
|
|
2698
|
-
this.options.log(`Realtime connection restored at Overleaf version ${currentVersion}.`);
|
|
2728
|
+
this.options.log(this.syncError ? `Realtime connection restored; ${this.syncError}` : `Realtime connection restored at Overleaf version ${currentVersion}.`);
|
|
2699
2729
|
}
|
|
2700
2730
|
schedule(path, source, actor) {
|
|
2701
2731
|
const normalized = normalizePath2(path);
|
|
@@ -2725,13 +2755,72 @@ var SyncEngine = class {
|
|
|
2725
2755
|
return;
|
|
2726
2756
|
}
|
|
2727
2757
|
const remoteActor = pending.unknownRemoteActor ? void 0 : pending.actors.size === 1 ? [...pending.actors][0] : pending.actors.size > 1 ? "multiple Overleaf collaborators" : void 0;
|
|
2728
|
-
this.queue = this.queue.then(() => this.
|
|
2729
|
-
this.
|
|
2758
|
+
this.queue = this.queue.then(() => this.synchronizePath(normalized, pending.source, remoteActor)).catch((error2) => {
|
|
2759
|
+
this.recordFailure(normalized, pending.source, error2, remoteActor);
|
|
2730
2760
|
});
|
|
2731
2761
|
}, 250)
|
|
2732
2762
|
};
|
|
2733
2763
|
this.eventTimers.set(normalized, pending);
|
|
2734
2764
|
}
|
|
2765
|
+
async synchronizePath(path, source, actor) {
|
|
2766
|
+
if (this.pendingInitialization && this.state.files[path] === void 0) {
|
|
2767
|
+
await this.initializePath(path);
|
|
2768
|
+
} else {
|
|
2769
|
+
await this.reconcilePath(path, source, actor);
|
|
2770
|
+
}
|
|
2771
|
+
if (this.failedPaths.delete(path)) {
|
|
2772
|
+
this.options.onSyncError?.(this.syncError);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
recordFailure(path, source, error2, actor) {
|
|
2776
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2777
|
+
this.failedPaths.set(path, { source, actor, message });
|
|
2778
|
+
this.options.log(`Failed to synchronize ${path}: ${message}; retrying without stopping other paths.`);
|
|
2779
|
+
this.options.onSyncError?.(this.syncError);
|
|
2780
|
+
this.scheduleRetry();
|
|
2781
|
+
}
|
|
2782
|
+
scheduleRetry() {
|
|
2783
|
+
if (this.stopped || this.retryTimer || this.failedPaths.size === 0) {
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
this.retryTimer = setTimeout(() => {
|
|
2787
|
+
this.retryTimer = void 0;
|
|
2788
|
+
void this.enqueue(async () => {
|
|
2789
|
+
if (this.stopped) {
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
for (const [path, failure] of [...this.failedPaths]) {
|
|
2793
|
+
if (this.stopped) {
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
try {
|
|
2797
|
+
await this.synchronizePath(path, failure.source, failure.actor);
|
|
2798
|
+
this.options.log(`Recovered synchronization for ${path}.`);
|
|
2799
|
+
} catch (error2) {
|
|
2800
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2801
|
+
this.failedPaths.set(path, { ...failure, message });
|
|
2802
|
+
this.options.log(`Retry failed for ${path}: ${message}`);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
await this.finishRemoteBatch();
|
|
2806
|
+
}).catch((error2) => this.options.log(`Could not checkpoint retry batch: ${String(error2)}`)).finally(() => {
|
|
2807
|
+
this.retryDelayMs = this.failedPaths.size ? Math.min(this.retryDelayMs * 2, 12e4) : 3e4;
|
|
2808
|
+
this.scheduleRetry();
|
|
2809
|
+
});
|
|
2810
|
+
}, this.retryDelayMs);
|
|
2811
|
+
}
|
|
2812
|
+
async finishRemoteBatch() {
|
|
2813
|
+
if (this.stopped || this.failedPaths.size || this.pendingRemoteVersion === void 0) {
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
this.state.remoteVersion = this.pendingRemoteVersion;
|
|
2817
|
+
if (this.pendingInitialization) {
|
|
2818
|
+
this.state.initialized = true;
|
|
2819
|
+
}
|
|
2820
|
+
await this.options.stateStore.save(this.state);
|
|
2821
|
+
this.pendingRemoteVersion = void 0;
|
|
2822
|
+
this.pendingInitialization = false;
|
|
2823
|
+
}
|
|
2735
2824
|
enqueue(operation) {
|
|
2736
2825
|
const result = this.queue.then(operation);
|
|
2737
2826
|
this.queue = result.then(() => void 0, () => void 0);
|
|
@@ -3194,6 +3283,7 @@ async function startServe(directory = process.cwd(), options = {}) {
|
|
|
3194
3283
|
remote,
|
|
3195
3284
|
stateStore: new FileSyncStateStore(root, settings.uri),
|
|
3196
3285
|
ignore,
|
|
3286
|
+
onSyncError: options.onSyncError,
|
|
3197
3287
|
log: options.log ?? log,
|
|
3198
3288
|
onConflict: options.onConflict ?? ((path, reason) => {
|
|
3199
3289
|
(options.log ?? log)(`Conflict paused for ${path}: ${reason}`);
|
|
@@ -3211,6 +3301,7 @@ async function startServe(directory = process.cwd(), options = {}) {
|
|
|
3211
3301
|
serverName: settings.serverName,
|
|
3212
3302
|
projectId: settings.projectId,
|
|
3213
3303
|
projectName: settings.projectName,
|
|
3304
|
+
syncError: () => engine.syncError,
|
|
3214
3305
|
conflicts: () => engine.getConflicts(),
|
|
3215
3306
|
resolveConflict: (path, winner) => engine.resolveConflict(path, winner),
|
|
3216
3307
|
retry: (path) => engine.retry(path),
|
|
@@ -3239,4 +3330,4 @@ export {
|
|
|
3239
3330
|
RemoteProject,
|
|
3240
3331
|
startServe
|
|
3241
3332
|
};
|
|
3242
|
-
//# sourceMappingURL=chunk-
|
|
3333
|
+
//# sourceMappingURL=chunk-3TTRDNHP.js.map
|