leafrelay 0.1.6 → 0.2.0
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/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/chunk-J46L4Q2T.js +5157 -0
- package/dist/chunk-J46L4Q2T.js.map +1 -0
- package/dist/{chunk-MNOPLM7M.js → chunk-PRWBFOB2.js} +187 -132
- package/dist/chunk-PRWBFOB2.js.map +1 -0
- package/dist/cli.js +21 -36
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +959 -0
- package/dist/daemon.js.map +1 -0
- package/dist/index.d.ts +11 -722
- package/dist/index.js +3 -69
- package/package.json +6 -3
- package/dist/chunk-MNOPLM7M.js.map +0 -1
- package/dist/cli.d.ts +0 -1
|
@@ -220,7 +220,7 @@ var OverleafRealtimeSocket = class {
|
|
|
220
220
|
handshakeUrl.searchParams.set(name, value);
|
|
221
221
|
}
|
|
222
222
|
handshakeUrl.searchParams.set("t", String(Date.now()));
|
|
223
|
-
const response = await fetch(handshakeUrl, {
|
|
223
|
+
const response = await (this.options.fetch ?? fetch)(handshakeUrl, {
|
|
224
224
|
headers: { origin: this.origin, cookie: this.cookie },
|
|
225
225
|
redirect: "manual"
|
|
226
226
|
});
|
|
@@ -373,11 +373,7 @@ var OverleafRealtimeSocket = class {
|
|
|
373
373
|
}, delay);
|
|
374
374
|
}
|
|
375
375
|
};
|
|
376
|
-
var overleafSocketProtocol = { encodePacket, decodePacket, decodePayload };
|
|
377
376
|
var sink;
|
|
378
|
-
function setLogSink(nextSink) {
|
|
379
|
-
sink = nextSink;
|
|
380
|
-
}
|
|
381
377
|
function write(level, args) {
|
|
382
378
|
if (sink !== void 0) {
|
|
383
379
|
sink(level, args);
|
|
@@ -389,57 +385,75 @@ function write(level, args) {
|
|
|
389
385
|
function log(...args) {
|
|
390
386
|
write("info", args);
|
|
391
387
|
}
|
|
392
|
-
function warn(...args) {
|
|
393
|
-
write("warn", args);
|
|
394
|
-
}
|
|
395
388
|
function error(...args) {
|
|
396
389
|
write("error", args);
|
|
397
390
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
var GLOBAL_MIN_REQUEST_INTERVAL_MS = 300;
|
|
402
|
-
function retryAfterHeaderMs(response) {
|
|
403
|
-
const retryAfter = response.headers?.get?.("retry-after");
|
|
404
|
-
if (retryAfter !== void 0 && retryAfter !== null) {
|
|
391
|
+
function retryAfterMilliseconds(response, now) {
|
|
392
|
+
const retryAfter = response.headers.get("retry-after");
|
|
393
|
+
if (retryAfter) {
|
|
405
394
|
const seconds = Number(retryAfter);
|
|
406
395
|
if (Number.isFinite(seconds)) {
|
|
407
396
|
return Math.max(1e3, seconds * 1e3);
|
|
408
397
|
}
|
|
409
398
|
const retryAt = Date.parse(retryAfter);
|
|
410
399
|
if (Number.isFinite(retryAt)) {
|
|
411
|
-
return Math.max(1e3, retryAt -
|
|
400
|
+
return Math.max(1e3, retryAt - now);
|
|
412
401
|
}
|
|
413
402
|
}
|
|
414
403
|
return 5e3;
|
|
415
404
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const delayMs = Math.max(
|
|
425
|
-
0,
|
|
426
|
-
globalNextRequestAt - Date.now(),
|
|
427
|
-
globalRateLimitedUntil - Date.now()
|
|
428
|
-
);
|
|
429
|
-
if (delayMs > 0) {
|
|
430
|
-
await new Promise((resolve6) => setTimeout(resolve6, delayMs));
|
|
431
|
-
}
|
|
432
|
-
globalNextRequestAt = Date.now() + GLOBAL_MIN_REQUEST_INTERVAL_MS;
|
|
433
|
-
const response = await fetch(url, init);
|
|
434
|
-
if (response.status === 429) {
|
|
435
|
-
globalRateLimitedUntil = Math.max(globalRateLimitedUntil, Date.now() + retryAfterHeaderMs(response));
|
|
436
|
-
log(`Global HTTP request queue entered rate-limit cooldown until ${new Date(globalRateLimitedUntil).toISOString()}.`);
|
|
437
|
-
}
|
|
438
|
-
return response;
|
|
439
|
-
} finally {
|
|
440
|
-
release();
|
|
405
|
+
var HttpRequestScheduler = class {
|
|
406
|
+
constructor(options = {}) {
|
|
407
|
+
this.options = options;
|
|
408
|
+
this.minimumIntervalMs = options.minimumIntervalMs ?? 300;
|
|
409
|
+
this.historyIntervalMs = options.historyIntervalMs ?? 3e3;
|
|
410
|
+
this.fetchImplementation = options.fetch ?? fetch;
|
|
411
|
+
this.now = options.now ?? Date.now;
|
|
412
|
+
this.wait = options.wait ?? ((milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)));
|
|
441
413
|
}
|
|
442
|
-
|
|
414
|
+
options;
|
|
415
|
+
chain = Promise.resolve();
|
|
416
|
+
nextRequestAt = 0;
|
|
417
|
+
historyNextRequestAt = 0;
|
|
418
|
+
rateLimitedUntil = 0;
|
|
419
|
+
minimumIntervalMs;
|
|
420
|
+
historyIntervalMs;
|
|
421
|
+
fetchImplementation;
|
|
422
|
+
now;
|
|
423
|
+
wait;
|
|
424
|
+
fetch(input, init) {
|
|
425
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
426
|
+
const history = url.includes("/updates?") || url.includes("/filetree/diff?") || url.includes("/diff?");
|
|
427
|
+
return this.enqueue(async () => {
|
|
428
|
+
const now = this.now();
|
|
429
|
+
const readyAt = Math.max(this.nextRequestAt, this.rateLimitedUntil, history ? this.historyNextRequestAt : 0);
|
|
430
|
+
if (readyAt > now) {
|
|
431
|
+
await this.wait(readyAt - now);
|
|
432
|
+
}
|
|
433
|
+
const requestAt = this.now();
|
|
434
|
+
this.nextRequestAt = requestAt + this.minimumIntervalMs;
|
|
435
|
+
if (history) {
|
|
436
|
+
this.historyNextRequestAt = requestAt + this.historyIntervalMs;
|
|
437
|
+
}
|
|
438
|
+
const response = await this.fetchImplementation(input, init);
|
|
439
|
+
if (response.status === 429) {
|
|
440
|
+
this.rateLimitedUntil = Math.max(
|
|
441
|
+
this.rateLimitedUntil,
|
|
442
|
+
this.now() + retryAfterMilliseconds(response, this.now())
|
|
443
|
+
);
|
|
444
|
+
this.options.onRateLimit?.(this.rateLimitedUntil);
|
|
445
|
+
log(`HTTP request scheduler entered rate-limit cooldown until ${new Date(this.rateLimitedUntil).toISOString()}.`);
|
|
446
|
+
}
|
|
447
|
+
return response;
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
enqueue(operation) {
|
|
451
|
+
const result = this.chain.then(operation);
|
|
452
|
+
this.chain = result.then(() => void 0, () => void 0);
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var sharedHttpRequestScheduler = new HttpRequestScheduler();
|
|
443
457
|
function getSetCookie(res) {
|
|
444
458
|
if (typeof res.headers?.getSetCookie === "function") {
|
|
445
459
|
return res.headers.getSetCookie();
|
|
@@ -451,31 +465,13 @@ function getSetCookie(res) {
|
|
|
451
465
|
return [];
|
|
452
466
|
}
|
|
453
467
|
var BaseAPI = class {
|
|
468
|
+
constructor(url, scheduler = sharedHttpRequestScheduler) {
|
|
469
|
+
this.scheduler = scheduler;
|
|
470
|
+
this.url = url;
|
|
471
|
+
}
|
|
472
|
+
scheduler;
|
|
454
473
|
url;
|
|
455
474
|
identity;
|
|
456
|
-
historyRequestChain = Promise.resolve();
|
|
457
|
-
lastHistoryRequestAt = 0;
|
|
458
|
-
isHistoryRoute(route) {
|
|
459
|
-
return route.includes("/updates?") || route.includes("/filetree/diff?") || route.includes("/diff?");
|
|
460
|
-
}
|
|
461
|
-
async waitForHistoryRequest() {
|
|
462
|
-
const previous = this.historyRequestChain;
|
|
463
|
-
let release;
|
|
464
|
-
this.historyRequestChain = new Promise((resolve6) => {
|
|
465
|
-
release = resolve6;
|
|
466
|
-
});
|
|
467
|
-
await previous;
|
|
468
|
-
try {
|
|
469
|
-
const minimumIntervalMs = 3e3;
|
|
470
|
-
const delayMs = Math.max(0, minimumIntervalMs - (Date.now() - this.lastHistoryRequestAt));
|
|
471
|
-
if (delayMs > 0) {
|
|
472
|
-
await new Promise((resolve6) => setTimeout(resolve6, delayMs));
|
|
473
|
-
}
|
|
474
|
-
this.lastHistoryRequestAt = Date.now();
|
|
475
|
-
} finally {
|
|
476
|
-
release();
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
475
|
retryAfterMs(response, attempt) {
|
|
480
476
|
const retryAfter = response.headers?.get?.("retry-after");
|
|
481
477
|
if (retryAfter !== void 0 && retryAfter !== null) {
|
|
@@ -490,11 +486,8 @@ var BaseAPI = class {
|
|
|
490
486
|
}
|
|
491
487
|
return 5e3 * Math.pow(2, attempt);
|
|
492
488
|
}
|
|
493
|
-
constructor(url) {
|
|
494
|
-
this.url = url;
|
|
495
|
-
}
|
|
496
489
|
async getCsrfToken() {
|
|
497
|
-
const res = await
|
|
490
|
+
const res = await this.scheduler.fetch(this.url + "login", {
|
|
498
491
|
method: "GET",
|
|
499
492
|
redirect: "manual"
|
|
500
493
|
});
|
|
@@ -509,7 +502,7 @@ var BaseAPI = class {
|
|
|
509
502
|
}
|
|
510
503
|
}
|
|
511
504
|
async getUserId(cookies) {
|
|
512
|
-
const res = await
|
|
505
|
+
const res = await this.scheduler.fetch(this.url + "project", {
|
|
513
506
|
method: "GET",
|
|
514
507
|
redirect: "manual",
|
|
515
508
|
headers: {
|
|
@@ -539,12 +532,13 @@ var BaseAPI = class {
|
|
|
539
532
|
reconnect: true,
|
|
540
533
|
reconnectionDelay: 1e3,
|
|
541
534
|
reconnectionLimit: 16e3,
|
|
542
|
-
maxReconnectionAttempts: 10
|
|
535
|
+
maxReconnectionAttempts: 10,
|
|
536
|
+
fetch: (input, init) => this.scheduler.fetch(input, init)
|
|
543
537
|
});
|
|
544
538
|
}
|
|
545
539
|
async passportLogin(email, password) {
|
|
546
540
|
const identity = await this.getCsrfToken();
|
|
547
|
-
const res = await
|
|
541
|
+
const res = await this.scheduler.fetch(this.url + "login", {
|
|
548
542
|
method: "POST",
|
|
549
543
|
redirect: "manual",
|
|
550
544
|
headers: {
|
|
@@ -603,7 +597,7 @@ var BaseAPI = class {
|
|
|
603
597
|
}
|
|
604
598
|
}
|
|
605
599
|
async updateCookies(identity) {
|
|
606
|
-
const res = await
|
|
600
|
+
const res = await this.scheduler.fetch(this.url + "socket.io/socket.io.js", {
|
|
607
601
|
method: "GET",
|
|
608
602
|
redirect: "manual",
|
|
609
603
|
headers: {
|
|
@@ -645,13 +639,10 @@ var BaseAPI = class {
|
|
|
645
639
|
let lastError = {};
|
|
646
640
|
for (let attempt = 0; attempt <= MAX_HTTP_RETRIES; attempt++) {
|
|
647
641
|
try {
|
|
648
|
-
if (this.isHistoryRoute(route)) {
|
|
649
|
-
await this.waitForHistoryRequest();
|
|
650
|
-
}
|
|
651
642
|
let res = void 0;
|
|
652
643
|
switch (type) {
|
|
653
644
|
case "GET":
|
|
654
|
-
res = await
|
|
645
|
+
res = await this.scheduler.fetch(this.url + route, {
|
|
655
646
|
method: "GET",
|
|
656
647
|
redirect: "manual",
|
|
657
648
|
headers: {
|
|
@@ -667,7 +658,7 @@ var BaseAPI = class {
|
|
|
667
658
|
_csrf: this.identity.csrfToken,
|
|
668
659
|
...body
|
|
669
660
|
});
|
|
670
|
-
res = await
|
|
661
|
+
res = await this.scheduler.fetch(this.url + route, {
|
|
671
662
|
method: "POST",
|
|
672
663
|
redirect: "manual",
|
|
673
664
|
headers: {
|
|
@@ -682,7 +673,7 @@ var BaseAPI = class {
|
|
|
682
673
|
case "PUT":
|
|
683
674
|
break;
|
|
684
675
|
case "DELETE":
|
|
685
|
-
res = await
|
|
676
|
+
res = await this.scheduler.fetch(this.url + route, {
|
|
686
677
|
method: "DELETE",
|
|
687
678
|
redirect: "manual",
|
|
688
679
|
headers: {
|
|
@@ -749,7 +740,7 @@ var BaseAPI = class {
|
|
|
749
740
|
}
|
|
750
741
|
let content = [];
|
|
751
742
|
while (true) {
|
|
752
|
-
const res = await
|
|
743
|
+
const res = await this.scheduler.fetch(this.url + route, {
|
|
753
744
|
method: "GET",
|
|
754
745
|
redirect: "manual",
|
|
755
746
|
headers: {
|
|
@@ -1030,7 +1021,7 @@ var BaseAPI = class {
|
|
|
1030
1021
|
}
|
|
1031
1022
|
let content = [];
|
|
1032
1023
|
while (true) {
|
|
1033
|
-
const res = await
|
|
1024
|
+
const res = await this.scheduler.fetch(absoluteUrl, {
|
|
1034
1025
|
method: "GET",
|
|
1035
1026
|
redirect: "manual",
|
|
1036
1027
|
headers
|
|
@@ -1517,14 +1508,8 @@ var SocketIOAPI = class _SocketIOAPI {
|
|
|
1517
1508
|
});
|
|
1518
1509
|
}
|
|
1519
1510
|
};
|
|
1520
|
-
var FolderKeys = {
|
|
1521
|
-
folder: "folders",
|
|
1522
|
-
doc: "docs",
|
|
1523
|
-
file: "fileRefs",
|
|
1524
|
-
outputs: "outputs"
|
|
1525
|
-
};
|
|
1526
1511
|
function configPath(environment = process.env) {
|
|
1527
|
-
return environment.LEAFRELAY_CONFIG ? resolve(environment.LEAFRELAY_CONFIG) : join(homedir(), ".leafrelay", "config.json");
|
|
1512
|
+
return environment.LEAFRELAY_CONFIG ? resolve(environment.LEAFRELAY_CONFIG) : join(environment.LEAFRELAY_HOME ? resolve(environment.LEAFRELAY_HOME) : join(homedir(), ".leafrelay"), "config.json");
|
|
1528
1513
|
}
|
|
1529
1514
|
function normalizeServerUrl(value) {
|
|
1530
1515
|
const url = new URL(value.includes("://") ? value : `https://${value}`);
|
|
@@ -1545,7 +1530,7 @@ async function readConfig(path = configPath()) {
|
|
|
1545
1530
|
return parsed;
|
|
1546
1531
|
} catch (error2) {
|
|
1547
1532
|
if (error2.code === "ENOENT") {
|
|
1548
|
-
return { version: 1, servers: {} };
|
|
1533
|
+
return { version: 1, servers: {}, knownServers: {} };
|
|
1549
1534
|
}
|
|
1550
1535
|
throw error2;
|
|
1551
1536
|
}
|
|
@@ -1558,7 +1543,29 @@ async function writeConfig(config, path = configPath()) {
|
|
|
1558
1543
|
}
|
|
1559
1544
|
async function saveServerSession(session, path = configPath()) {
|
|
1560
1545
|
const config = await readConfig(path);
|
|
1561
|
-
|
|
1546
|
+
const key = serverKey(session.url);
|
|
1547
|
+
config.servers[key] = session;
|
|
1548
|
+
config.knownServers ??= {};
|
|
1549
|
+
config.knownServers[key] ??= { name: key, url: session.url };
|
|
1550
|
+
await writeConfig(config, path);
|
|
1551
|
+
}
|
|
1552
|
+
async function saveKnownServer(name, url, path = configPath()) {
|
|
1553
|
+
const config = await readConfig(path);
|
|
1554
|
+
const key = serverKey(url);
|
|
1555
|
+
config.knownServers ??= {};
|
|
1556
|
+
config.knownServers[key] = { name, url: normalizeServerUrl(url) };
|
|
1557
|
+
await writeConfig(config, path);
|
|
1558
|
+
}
|
|
1559
|
+
async function removeKnownServer(server, path = configPath()) {
|
|
1560
|
+
const config = await readConfig(path);
|
|
1561
|
+
const key = serverKey(server);
|
|
1562
|
+
delete config.servers[key];
|
|
1563
|
+
delete config.knownServers?.[key];
|
|
1564
|
+
await writeConfig(config, path);
|
|
1565
|
+
}
|
|
1566
|
+
async function removeServerSession(server, path = configPath()) {
|
|
1567
|
+
const config = await readConfig(path);
|
|
1568
|
+
delete config.servers[serverKey(server)];
|
|
1562
1569
|
await writeConfig(config, path);
|
|
1563
1570
|
}
|
|
1564
1571
|
async function getServerSession(server, environment = process.env, path = configPath(environment)) {
|
|
@@ -1744,11 +1751,12 @@ function textContent(content) {
|
|
|
1744
1751
|
}
|
|
1745
1752
|
}
|
|
1746
1753
|
var RemoteProject = class {
|
|
1747
|
-
constructor(url, projectId, cookies) {
|
|
1754
|
+
constructor(url, projectId, cookies, transport) {
|
|
1748
1755
|
this.url = url;
|
|
1749
1756
|
this.projectId = projectId;
|
|
1750
1757
|
this.cookies = cookies;
|
|
1751
|
-
this.
|
|
1758
|
+
this.externalTransport = transport;
|
|
1759
|
+
this.api = transport?.api ?? new BaseAPI(url);
|
|
1752
1760
|
}
|
|
1753
1761
|
url;
|
|
1754
1762
|
projectId;
|
|
@@ -1760,19 +1768,27 @@ var RemoteProject = class {
|
|
|
1760
1768
|
entries = /* @__PURE__ */ new Map();
|
|
1761
1769
|
idToPath = /* @__PURE__ */ new Map();
|
|
1762
1770
|
changeHandler;
|
|
1771
|
+
externalTransport;
|
|
1763
1772
|
async connect() {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1773
|
+
if (this.externalTransport) {
|
|
1774
|
+
this.identity = this.externalTransport.identity;
|
|
1775
|
+
this.socket = this.externalTransport.socket;
|
|
1776
|
+
} else {
|
|
1777
|
+
const login = await this.api.cookiesLogin(this.cookies);
|
|
1778
|
+
if (login.type !== "success" || !login.identity) {
|
|
1779
|
+
throw new Error(login.message || `Authentication to ${this.url} failed.`);
|
|
1780
|
+
}
|
|
1781
|
+
this.identity = login.identity;
|
|
1782
|
+
this.socket = new SocketIOAPI(this.url, this.api, this.identity, this.projectId);
|
|
1767
1783
|
}
|
|
1768
|
-
this.identity = login.identity;
|
|
1769
|
-
this.socket = new SocketIOAPI(this.url, this.api, this.identity, this.projectId);
|
|
1770
1784
|
this.project = await this.socket.joinProject(this.projectId);
|
|
1771
1785
|
this.reindex();
|
|
1772
1786
|
this.registerEvents();
|
|
1773
1787
|
}
|
|
1774
1788
|
disconnect() {
|
|
1775
|
-
this.
|
|
1789
|
+
if (!this.externalTransport) {
|
|
1790
|
+
this.socket?.disconnect();
|
|
1791
|
+
}
|
|
1776
1792
|
}
|
|
1777
1793
|
onChange(handler) {
|
|
1778
1794
|
this.changeHandler = handler;
|
|
@@ -2205,6 +2221,49 @@ var SyncEngine = class {
|
|
|
2205
2221
|
await this.stopLocalWatch?.();
|
|
2206
2222
|
await this.queue;
|
|
2207
2223
|
}
|
|
2224
|
+
getConflicts() {
|
|
2225
|
+
return [...this.conflicts].sort();
|
|
2226
|
+
}
|
|
2227
|
+
async resolveConflict(path, winner) {
|
|
2228
|
+
const normalized = normalizePath2(path);
|
|
2229
|
+
if (!this.conflicts.has(normalized)) {
|
|
2230
|
+
throw new Error(`No unresolved conflict exists for ${normalized}.`);
|
|
2231
|
+
}
|
|
2232
|
+
await this.enqueue(async () => {
|
|
2233
|
+
const remoteEntry = this.options.remote.entry(normalized);
|
|
2234
|
+
if (winner === "local") {
|
|
2235
|
+
const localContent = await this.options.local.read(normalized);
|
|
2236
|
+
if (localContent) {
|
|
2237
|
+
await this.options.remote.write(normalized, localContent);
|
|
2238
|
+
const synchronized = await this.options.remote.read(normalized);
|
|
2239
|
+
if (sha256(synchronized) !== sha256(localContent)) {
|
|
2240
|
+
await this.options.local.write(normalized, synchronized);
|
|
2241
|
+
}
|
|
2242
|
+
updateSyncCheckpoint(this.state, normalized, synchronized);
|
|
2243
|
+
} else {
|
|
2244
|
+
if (remoteEntry) {
|
|
2245
|
+
await this.options.remote.remove(normalized);
|
|
2246
|
+
}
|
|
2247
|
+
removeSyncCheckpoint(this.state, normalized);
|
|
2248
|
+
}
|
|
2249
|
+
} else if (isRemoteFile(remoteEntry)) {
|
|
2250
|
+
const remoteContent = await this.options.remote.read(normalized);
|
|
2251
|
+
await this.options.local.write(normalized, remoteContent);
|
|
2252
|
+
updateSyncCheckpoint(this.state, normalized, remoteContent);
|
|
2253
|
+
} else {
|
|
2254
|
+
await this.options.local.remove(normalized);
|
|
2255
|
+
removeSyncCheckpoint(this.state, normalized);
|
|
2256
|
+
}
|
|
2257
|
+
await this.options.stateStore.save(this.state);
|
|
2258
|
+
this.conflicts.delete(normalized);
|
|
2259
|
+
this.options.log(`Resolved ${normalized} using ${winner === "local" ? "local" : "Overleaf"} content.`);
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
async retry(path) {
|
|
2263
|
+
const normalized = normalizePath2(path);
|
|
2264
|
+
this.conflicts.delete(normalized);
|
|
2265
|
+
await this.enqueue(() => this.reconcilePath(normalized, "local"));
|
|
2266
|
+
}
|
|
2208
2267
|
async startupReconcile(currentVersion, localFiles, remoteFiles) {
|
|
2209
2268
|
const state = this.state;
|
|
2210
2269
|
if (state.remoteVersion > currentVersion) {
|
|
@@ -2256,11 +2315,14 @@ var SyncEngine = class {
|
|
|
2256
2315
|
this.options.log(`Failed to synchronize ${path}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2257
2316
|
}
|
|
2258
2317
|
}
|
|
2259
|
-
if (failed
|
|
2260
|
-
throw new Error(
|
|
2318
|
+
if (failed) {
|
|
2319
|
+
throw new Error("Synchronization paused for failed paths; successful paths were checkpointed.");
|
|
2261
2320
|
}
|
|
2262
2321
|
state.remoteVersion = currentVersion;
|
|
2263
2322
|
await this.options.stateStore.save(state);
|
|
2323
|
+
if (this.conflicts.size !== 0) {
|
|
2324
|
+
this.options.log(`Synchronization remains active; ${this.conflicts.size} conflicting path(s) are paused.`);
|
|
2325
|
+
}
|
|
2264
2326
|
}
|
|
2265
2327
|
schedule(path, source) {
|
|
2266
2328
|
const normalized = normalizePath2(path);
|
|
@@ -2278,6 +2340,11 @@ var SyncEngine = class {
|
|
|
2278
2340
|
});
|
|
2279
2341
|
}, 250));
|
|
2280
2342
|
}
|
|
2343
|
+
enqueue(operation) {
|
|
2344
|
+
const result = this.queue.then(operation);
|
|
2345
|
+
this.queue = result.then(() => void 0, () => void 0);
|
|
2346
|
+
return result;
|
|
2347
|
+
}
|
|
2281
2348
|
async reconcilePath(path, source) {
|
|
2282
2349
|
const normalized = normalizePath2(path);
|
|
2283
2350
|
if (this.shouldIgnore(normalized) || this.conflicts.has(normalized)) {
|
|
@@ -2391,15 +2458,15 @@ var FileSyncStateStore = class {
|
|
|
2391
2458
|
this.serialized = serialized;
|
|
2392
2459
|
}
|
|
2393
2460
|
};
|
|
2394
|
-
async function startServe(directory = process.cwd()) {
|
|
2461
|
+
async function startServe(directory = process.cwd(), options = {}) {
|
|
2395
2462
|
const root = resolve5(directory);
|
|
2396
2463
|
const settings = await readProjectSettings(root);
|
|
2397
2464
|
const session = await getServerSession(settings.serverName);
|
|
2398
2465
|
if (!session) {
|
|
2399
2466
|
throw new Error(`No login is stored for ${settings.serverName}. Run: leafrelay login ${settings.serverName}`);
|
|
2400
2467
|
}
|
|
2401
|
-
const cookie = process.env.LEAFRELAY_COOKIE || session.identity.cookies;
|
|
2402
|
-
const remote = new RemoteProject(session.url || settings.serverUrl, settings.projectId, cookie);
|
|
2468
|
+
const cookie = options.cookie || process.env.LEAFRELAY_COOKIE || session.identity.cookies;
|
|
2469
|
+
const remote = options.remote ?? new RemoteProject(session.url || settings.serverUrl, settings.projectId, cookie);
|
|
2403
2470
|
await remote.connect();
|
|
2404
2471
|
const configuredPatterns = settings.localReplica.settings["ignore-patterns"];
|
|
2405
2472
|
const ignore = createIgnoreMatcher(configuredPatterns);
|
|
@@ -2410,10 +2477,10 @@ async function startServe(directory = process.cwd()) {
|
|
|
2410
2477
|
remote,
|
|
2411
2478
|
stateStore: new FileSyncStateStore(root, settings.uri),
|
|
2412
2479
|
ignore,
|
|
2413
|
-
log,
|
|
2414
|
-
onConflict: (path, reason) => {
|
|
2415
|
-
log(`Conflict paused for ${path}: ${reason}`);
|
|
2416
|
-
}
|
|
2480
|
+
log: options.log ?? log,
|
|
2481
|
+
onConflict: options.onConflict ?? ((path, reason) => {
|
|
2482
|
+
(options.log ?? log)(`Conflict paused for ${path}: ${reason}`);
|
|
2483
|
+
})
|
|
2417
2484
|
});
|
|
2418
2485
|
try {
|
|
2419
2486
|
await engine.start();
|
|
@@ -2421,8 +2488,15 @@ async function startServe(directory = process.cwd()) {
|
|
|
2421
2488
|
remote.disconnect();
|
|
2422
2489
|
throw error2;
|
|
2423
2490
|
}
|
|
2424
|
-
log(`LeafRelay is serving ${root} (${settings.projectName}).`);
|
|
2491
|
+
(options.log ?? log)(`LeafRelay is serving ${root} (${settings.projectName}).`);
|
|
2425
2492
|
return {
|
|
2493
|
+
root,
|
|
2494
|
+
serverName: settings.serverName,
|
|
2495
|
+
projectId: settings.projectId,
|
|
2496
|
+
projectName: settings.projectName,
|
|
2497
|
+
conflicts: () => engine.getConflicts(),
|
|
2498
|
+
resolveConflict: (path, winner) => engine.resolveConflict(path, winner),
|
|
2499
|
+
retry: (path) => engine.retry(path),
|
|
2426
2500
|
stop: async () => {
|
|
2427
2501
|
await engine.stop();
|
|
2428
2502
|
remote.disconnect();
|
|
@@ -2431,39 +2505,20 @@ async function startServe(directory = process.cwd()) {
|
|
|
2431
2505
|
}
|
|
2432
2506
|
|
|
2433
2507
|
export {
|
|
2434
|
-
|
|
2435
|
-
overleafSocketProtocol,
|
|
2436
|
-
setLogSink,
|
|
2437
|
-
log,
|
|
2438
|
-
warn,
|
|
2439
|
-
error,
|
|
2440
|
-
BaseAPI,
|
|
2508
|
+
HttpRequestScheduler,
|
|
2441
2509
|
ExtendedBaseAPI,
|
|
2442
2510
|
SocketIOAPI,
|
|
2443
|
-
FolderKeys,
|
|
2444
2511
|
configPath,
|
|
2445
2512
|
normalizeServerUrl,
|
|
2446
2513
|
serverKey,
|
|
2447
2514
|
readConfig,
|
|
2448
|
-
writeConfig,
|
|
2449
2515
|
saveServerSession,
|
|
2516
|
+
saveKnownServer,
|
|
2517
|
+
removeKnownServer,
|
|
2518
|
+
removeServerSession,
|
|
2450
2519
|
getServerSession,
|
|
2451
|
-
NodeReplicaFileSystem,
|
|
2452
2520
|
readProjectSettings,
|
|
2453
|
-
DEFAULT_IGNORE_PATTERNS,
|
|
2454
|
-
createIgnoreMatcher,
|
|
2455
2521
|
RemoteProject,
|
|
2456
|
-
encodeTextBase,
|
|
2457
|
-
mergeText,
|
|
2458
|
-
SYNC_STATE_SCHEMA_VERSION,
|
|
2459
|
-
sha256,
|
|
2460
|
-
parseSyncState,
|
|
2461
|
-
createSyncState,
|
|
2462
|
-
updateSyncCheckpoint,
|
|
2463
|
-
removeSyncCheckpoint,
|
|
2464
|
-
readTextBase,
|
|
2465
|
-
SyncEngine,
|
|
2466
|
-
FileSyncStateStore,
|
|
2467
2522
|
startServe
|
|
2468
2523
|
};
|
|
2469
|
-
//# sourceMappingURL=chunk-
|
|
2524
|
+
//# sourceMappingURL=chunk-PRWBFOB2.js.map
|