framer-code-link 0.14.0 → 0.16.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/dist/index.mjs +119 -67
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5,10 +5,10 @@ import fs from "fs/promises";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { WebSocketServer } from "ws";
|
|
7
7
|
import { createHash } from "crypto";
|
|
8
|
-
import { setupTypeAcquisition } from "@typescript/ata";
|
|
9
|
-
import ts from "typescript";
|
|
10
8
|
import { execSync } from "child_process";
|
|
11
9
|
import fs$1 from "fs";
|
|
10
|
+
import { setupTypeAcquisition } from "@typescript/ata";
|
|
11
|
+
import ts from "typescript";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import chokidar from "chokidar";
|
|
14
14
|
|
|
@@ -509,6 +509,7 @@ function initConnection(port) {
|
|
|
509
509
|
wss.on("listening", () => {
|
|
510
510
|
isReady = true;
|
|
511
511
|
debug(`WebSocket server listening on port ${port}`);
|
|
512
|
+
let activeClient = null;
|
|
512
513
|
wss.on("connection", (ws) => {
|
|
513
514
|
const connId = ++connectionId;
|
|
514
515
|
let handshakeReceived = false;
|
|
@@ -519,8 +520,15 @@ function initConnection(port) {
|
|
|
519
520
|
if (message.type === "handshake") {
|
|
520
521
|
debug(`Received handshake (conn ${connId})`);
|
|
521
522
|
handshakeReceived = true;
|
|
523
|
+
const previousActiveClient = activeClient;
|
|
524
|
+
activeClient = ws;
|
|
525
|
+
if (previousActiveClient && previousActiveClient !== activeClient) {
|
|
526
|
+
debug(`Replacing active client with conn ${connId}`);
|
|
527
|
+
if (previousActiveClient.readyState === READY_STATE.OPEN || previousActiveClient.readyState === READY_STATE.CONNECTING) previousActiveClient.close();
|
|
528
|
+
}
|
|
522
529
|
handlers.onHandshake?.(ws, message);
|
|
523
|
-
} else if (handshakeReceived) handlers.onMessage?.(message);
|
|
530
|
+
} else if (handshakeReceived && activeClient === ws) handlers.onMessage?.(message);
|
|
531
|
+
else if (handshakeReceived) debug(`Ignoring ${message.type} from stale client (conn ${connId})`);
|
|
524
532
|
else debug(`Ignoring ${message.type} before handshake (conn ${connId})`);
|
|
525
533
|
} catch (err) {
|
|
526
534
|
error(`Failed to parse message:`, err);
|
|
@@ -528,7 +536,10 @@ function initConnection(port) {
|
|
|
528
536
|
});
|
|
529
537
|
ws.on("close", (code, reason) => {
|
|
530
538
|
debug(`Client disconnected (code: ${code}, reason: ${reason.toString()})`);
|
|
531
|
-
|
|
539
|
+
if (activeClient === ws) {
|
|
540
|
+
activeClient = null;
|
|
541
|
+
handlers.onDisconnect?.(ws);
|
|
542
|
+
} else debug(`Ignoring disconnect from stale client (conn ${connId})`);
|
|
532
543
|
});
|
|
533
544
|
ws.on("error", (err) => {
|
|
534
545
|
error(`WebSocket error:`, err);
|
|
@@ -680,7 +691,7 @@ async function loadPersistedState(projectDir) {
|
|
|
680
691
|
if (normalizedName !== fileName) debug(`Normalized persisted key "${fileName}" -> "${normalizedName}" for compatibility`);
|
|
681
692
|
result.set(normalizedName, state);
|
|
682
693
|
}
|
|
683
|
-
debug(`Loaded persisted state for ${result.size}
|
|
694
|
+
debug(`Loaded persisted state for ${pluralize(result.size, "file")}`);
|
|
684
695
|
return result;
|
|
685
696
|
} catch (err) {
|
|
686
697
|
if (err.code === "ENOENT") {
|
|
@@ -702,7 +713,7 @@ async function savePersistedState(projectDir, state) {
|
|
|
702
713
|
};
|
|
703
714
|
try {
|
|
704
715
|
await fs.writeFile(statePath, JSON.stringify(persistedState, null, 2));
|
|
705
|
-
debug(`Saved persisted state for ${state.size}
|
|
716
|
+
debug(`Saved persisted state for ${pluralize(state.size, "file")}`);
|
|
706
717
|
} catch (err) {
|
|
707
718
|
warn("Failed to save persisted state:", err);
|
|
708
719
|
}
|
|
@@ -773,7 +784,7 @@ async function detectConflicts(remoteFiles, filesDir, options = {}) {
|
|
|
773
784
|
const preferRemote = options.preferRemote ?? false;
|
|
774
785
|
const persistedState = options.persistedState;
|
|
775
786
|
const getPersistedState = (fileName) => persistedState?.get(fileKeyForLookup(fileName));
|
|
776
|
-
debug(`Detecting conflicts for ${
|
|
787
|
+
debug(`Detecting conflicts for ${pluralize(remoteFiles.length, "remote file")}`);
|
|
777
788
|
const localFiles = await listFiles(filesDir);
|
|
778
789
|
const localFileMap = new Map(localFiles.map((f) => [fileKeyForLookup(f.name), f]));
|
|
779
790
|
const remoteFileMap = new Map(remoteFiles.map((f) => {
|
|
@@ -927,7 +938,7 @@ function autoResolveConflicts(conflicts, versions, options = {}) {
|
|
|
927
938
|
* CRITICAL: Update hashTracker BEFORE writing to disk
|
|
928
939
|
*/
|
|
929
940
|
async function writeRemoteFiles(files, filesDir, hashTracker, installer) {
|
|
930
|
-
debug(`Writing ${files.length
|
|
941
|
+
debug(`Writing ${pluralize(files.length, "remote file")}`);
|
|
931
942
|
for (const file of files) try {
|
|
932
943
|
const normalized = resolveRemoteReference(filesDir, file.name);
|
|
933
944
|
const fullPath = normalized.absolutePath;
|
|
@@ -1002,49 +1013,6 @@ function isSupportedExtension(fileName) {
|
|
|
1002
1013
|
return SUPPORTED_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
1003
1014
|
}
|
|
1004
1015
|
|
|
1005
|
-
//#endregion
|
|
1006
|
-
//#region src/utils/imports.ts
|
|
1007
|
-
/**
|
|
1008
|
-
* Extract npm and URL-based imports from source code.
|
|
1009
|
-
*/
|
|
1010
|
-
function extractImports(code) {
|
|
1011
|
-
const imports = [];
|
|
1012
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1013
|
-
const npmRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]([^./][^'"]+)['"]/g;
|
|
1014
|
-
const urlRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]https?:\/\/[^'"]+['"]/g;
|
|
1015
|
-
let match;
|
|
1016
|
-
while ((match = npmRegex.exec(code)) !== null) {
|
|
1017
|
-
const pkgName = match[1];
|
|
1018
|
-
const normalized = pkgName.startsWith("@") ? pkgName.split("/").slice(0, 2).join("/") : pkgName.split("/")[0];
|
|
1019
|
-
if (!seen.has(normalized)) {
|
|
1020
|
-
seen.add(normalized);
|
|
1021
|
-
imports.push({
|
|
1022
|
-
type: "npm",
|
|
1023
|
-
name: normalized,
|
|
1024
|
-
raw: match[0]
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
while ((match = urlRegex.exec(code)) !== null) {
|
|
1029
|
-
const pkgName = extractPackageFromUrl(match[0]);
|
|
1030
|
-
if (pkgName && !seen.has(pkgName)) {
|
|
1031
|
-
seen.add(pkgName);
|
|
1032
|
-
imports.push({
|
|
1033
|
-
type: "url",
|
|
1034
|
-
name: pkgName,
|
|
1035
|
-
raw: match[0]
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
return imports;
|
|
1040
|
-
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Attempt to derive an npm-style package specifier from a URL import.
|
|
1043
|
-
*/
|
|
1044
|
-
function extractPackageFromUrl(url) {
|
|
1045
|
-
return /\/(@?[^@/]+(?:\/[^@/]+)?)/.exec(url)?.[1] ?? null;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
1016
|
//#endregion
|
|
1049
1017
|
//#region src/helpers/git.ts
|
|
1050
1018
|
/**
|
|
@@ -1124,6 +1092,49 @@ function tryGitInit(projectDir) {
|
|
|
1124
1092
|
}
|
|
1125
1093
|
}
|
|
1126
1094
|
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region src/utils/imports.ts
|
|
1097
|
+
/**
|
|
1098
|
+
* Extract npm and URL-based imports from source code.
|
|
1099
|
+
*/
|
|
1100
|
+
function extractImports(code) {
|
|
1101
|
+
const imports = [];
|
|
1102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1103
|
+
const npmRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]([^./][^'"]+)['"]/g;
|
|
1104
|
+
const urlRegex = /import\s+(?:(?:\*\s+as\s+\w+)|(?:\w+)|(?:\{[^}]*\}))\s+from\s+['"]https?:\/\/[^'"]+['"]/g;
|
|
1105
|
+
let match;
|
|
1106
|
+
while ((match = npmRegex.exec(code)) !== null) {
|
|
1107
|
+
const pkgName = match[1];
|
|
1108
|
+
const normalized = pkgName.startsWith("@") ? pkgName.split("/").slice(0, 2).join("/") : pkgName.split("/")[0];
|
|
1109
|
+
if (!seen.has(normalized)) {
|
|
1110
|
+
seen.add(normalized);
|
|
1111
|
+
imports.push({
|
|
1112
|
+
type: "npm",
|
|
1113
|
+
name: normalized,
|
|
1114
|
+
raw: match[0]
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
while ((match = urlRegex.exec(code)) !== null) {
|
|
1119
|
+
const pkgName = extractPackageFromUrl(match[0]);
|
|
1120
|
+
if (pkgName && !seen.has(pkgName)) {
|
|
1121
|
+
seen.add(pkgName);
|
|
1122
|
+
imports.push({
|
|
1123
|
+
type: "url",
|
|
1124
|
+
name: pkgName,
|
|
1125
|
+
raw: match[0]
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return imports;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Attempt to derive an npm-style package specifier from a URL import.
|
|
1133
|
+
*/
|
|
1134
|
+
function extractPackageFromUrl(url) {
|
|
1135
|
+
return /\/(@?[^@/]+(?:\/[^@/]+)?)/.exec(url)?.[1] ?? null;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1127
1138
|
//#endregion
|
|
1128
1139
|
//#region src/helpers/skills.ts
|
|
1129
1140
|
/**
|
|
@@ -1346,7 +1357,6 @@ var Installer = class {
|
|
|
1346
1357
|
this.ensureSkills(),
|
|
1347
1358
|
this.ensureGitignore()
|
|
1348
1359
|
]);
|
|
1349
|
-
tryGitInit(this.projectDir);
|
|
1350
1360
|
Promise.resolve().then(async () => {
|
|
1351
1361
|
await this.ensureReact18Types();
|
|
1352
1362
|
const coreImports = CORE_LIBRARIES.map((lib) => `import "${lib}";`).join("\n");
|
|
@@ -1508,6 +1518,8 @@ declare module "*.json"
|
|
|
1508
1518
|
} catch {}
|
|
1509
1519
|
const content = [
|
|
1510
1520
|
"node_modules/",
|
|
1521
|
+
".DS_Store",
|
|
1522
|
+
"*.local",
|
|
1511
1523
|
"",
|
|
1512
1524
|
"# Framer Code Link",
|
|
1513
1525
|
".framer-sync-state.json",
|
|
@@ -1993,7 +2005,8 @@ async function getProjectHashFromCwd() {
|
|
|
1993
2005
|
return null;
|
|
1994
2006
|
}
|
|
1995
2007
|
}
|
|
1996
|
-
async function findOrCreateProjectDirectory(
|
|
2008
|
+
async function findOrCreateProjectDirectory(options) {
|
|
2009
|
+
const { projectHash, projectName, explicitDirectory, baseDirectory } = options;
|
|
1997
2010
|
if (explicitDirectory) {
|
|
1998
2011
|
const resolved = path.resolve(explicitDirectory);
|
|
1999
2012
|
await fs.mkdir(path.join(resolved, "files"), { recursive: true });
|
|
@@ -2002,7 +2015,7 @@ async function findOrCreateProjectDirectory(projectHash, projectName, explicitDi
|
|
|
2002
2015
|
created: false
|
|
2003
2016
|
};
|
|
2004
2017
|
}
|
|
2005
|
-
const cwd = process.cwd();
|
|
2018
|
+
const cwd = baseDirectory ?? process.cwd();
|
|
2006
2019
|
const existing = await findExistingProjectDirectory(cwd, projectHash);
|
|
2007
2020
|
if (existing) return {
|
|
2008
2021
|
directory: existing,
|
|
@@ -2012,7 +2025,7 @@ async function findOrCreateProjectDirectory(projectHash, projectName, explicitDi
|
|
|
2012
2025
|
const directoryName = toDirectoryName(projectName);
|
|
2013
2026
|
const pkgName = toPackageName(projectName);
|
|
2014
2027
|
const shortId = shortProjectHash(projectHash);
|
|
2015
|
-
const projectDirectory =
|
|
2028
|
+
const { directory: projectDirectory, nameCollision } = await findAvailableDirectory(cwd, directoryName || `project-${shortId}`, shortId);
|
|
2016
2029
|
await fs.mkdir(path.join(projectDirectory, "files"), { recursive: true });
|
|
2017
2030
|
const pkg = {
|
|
2018
2031
|
name: pkgName || shortId,
|
|
@@ -2024,9 +2037,29 @@ async function findOrCreateProjectDirectory(projectHash, projectName, explicitDi
|
|
|
2024
2037
|
await fs.writeFile(path.join(projectDirectory, "package.json"), JSON.stringify(pkg, null, 2));
|
|
2025
2038
|
return {
|
|
2026
2039
|
directory: projectDirectory,
|
|
2027
|
-
created: true
|
|
2040
|
+
created: true,
|
|
2041
|
+
nameCollision
|
|
2028
2042
|
};
|
|
2029
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Returns a directory path that doesn't collide with an existing project.
|
|
2046
|
+
* Tries the bare name first, falls back to name-{shortId} if taken.
|
|
2047
|
+
*/
|
|
2048
|
+
async function findAvailableDirectory(baseDir, name, shortId) {
|
|
2049
|
+
const candidate = path.join(baseDir, name);
|
|
2050
|
+
try {
|
|
2051
|
+
await fs.access(candidate);
|
|
2052
|
+
return {
|
|
2053
|
+
directory: path.join(baseDir, `${name}-${shortId}`),
|
|
2054
|
+
nameCollision: true
|
|
2055
|
+
};
|
|
2056
|
+
} catch {
|
|
2057
|
+
return {
|
|
2058
|
+
directory: candidate,
|
|
2059
|
+
nameCollision: false
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2030
2063
|
async function findExistingProjectDirectory(baseDirectory, projectHash) {
|
|
2031
2064
|
if (await matchesProject(path.join(baseDirectory, "package.json"), projectHash)) return baseDirectory;
|
|
2032
2065
|
const entries = await fs.readdir(baseDirectory, { withFileTypes: true });
|
|
@@ -2140,7 +2173,7 @@ function transition(state, event) {
|
|
|
2140
2173
|
effects
|
|
2141
2174
|
};
|
|
2142
2175
|
}
|
|
2143
|
-
effects.push(log("debug", `Received file list: ${event.files.length}
|
|
2176
|
+
effects.push(log("debug", `Received file list: ${pluralize(event.files.length, "file")}`));
|
|
2144
2177
|
effects.push({
|
|
2145
2178
|
type: "DETECT_CONFLICTS",
|
|
2146
2179
|
remoteFiles: event.files
|
|
@@ -2162,13 +2195,13 @@ function transition(state, event) {
|
|
|
2162
2195
|
};
|
|
2163
2196
|
}
|
|
2164
2197
|
const { conflicts, safeWrites, localOnly } = event;
|
|
2165
|
-
if (safeWrites.length > 0) effects.push(log("debug", `Applying ${safeWrites.length} safe writes`), {
|
|
2198
|
+
if (safeWrites.length > 0) effects.push(log("debug", `Applying ${safeWrites.length} safe writes`), log("success", `Applied ${pluralize(safeWrites.length, "file")} during sync`), {
|
|
2166
2199
|
type: "WRITE_FILES",
|
|
2167
2200
|
files: safeWrites,
|
|
2168
2201
|
silent: true
|
|
2169
2202
|
});
|
|
2170
2203
|
if (localOnly.length > 0) {
|
|
2171
|
-
effects.push(log("debug", `Uploading ${localOnly.length
|
|
2204
|
+
effects.push(log("debug", `Uploading ${pluralize(localOnly.length, "local-only file")}`));
|
|
2172
2205
|
for (const file of localOnly) effects.push({
|
|
2173
2206
|
type: "SEND_MESSAGE",
|
|
2174
2207
|
payload: {
|
|
@@ -2455,9 +2488,14 @@ async function executeEffect(effect, context) {
|
|
|
2455
2488
|
case "INIT_WORKSPACE":
|
|
2456
2489
|
if (!config.projectDir) {
|
|
2457
2490
|
const projectName = config.explicitName ?? effect.projectInfo.projectName;
|
|
2458
|
-
const directoryInfo = await findOrCreateProjectDirectory(
|
|
2491
|
+
const directoryInfo = await findOrCreateProjectDirectory({
|
|
2492
|
+
projectHash: config.projectHash,
|
|
2493
|
+
projectName,
|
|
2494
|
+
explicitDirectory: config.explicitDirectory
|
|
2495
|
+
});
|
|
2459
2496
|
config.projectDir = directoryInfo.directory;
|
|
2460
2497
|
config.projectDirCreated = directoryInfo.created;
|
|
2498
|
+
if (directoryInfo.nameCollision) warn(`Folder ${projectName} already exists`);
|
|
2461
2499
|
config.filesDir = `${config.projectDir}/files`;
|
|
2462
2500
|
debug(`Files directory: ${config.filesDir}`);
|
|
2463
2501
|
await fs.mkdir(config.filesDir, { recursive: true });
|
|
@@ -2466,7 +2504,7 @@ async function executeEffect(effect, context) {
|
|
|
2466
2504
|
case "LOAD_PERSISTED_STATE":
|
|
2467
2505
|
if (config.projectDir) {
|
|
2468
2506
|
await fileMetadataCache.initialize(config.projectDir);
|
|
2469
|
-
debug(`Loaded persisted metadata for ${fileMetadataCache.size()}
|
|
2507
|
+
debug(`Loaded persisted metadata for ${pluralize(fileMetadataCache.size(), "file")}`);
|
|
2470
2508
|
}
|
|
2471
2509
|
return [];
|
|
2472
2510
|
case "LIST_LOCAL_FILES": {
|
|
@@ -2585,6 +2623,7 @@ async function executeEffect(effect, context) {
|
|
|
2585
2623
|
for (const fileName of confirmedFiles) {
|
|
2586
2624
|
hashTracker.forget(fileName);
|
|
2587
2625
|
fileMetadataCache.recordDelete(fileName);
|
|
2626
|
+
fileDelete(fileName);
|
|
2588
2627
|
}
|
|
2589
2628
|
if (confirmedFiles.length > 0 && syncState.socket) await sendMessage(syncState.socket, {
|
|
2590
2629
|
type: "file-delete",
|
|
@@ -2603,7 +2642,7 @@ async function executeEffect(effect, context) {
|
|
|
2603
2642
|
if (syncState.socket) await sendMessage(syncState.socket, { type: "sync-complete" });
|
|
2604
2643
|
if (wasDisconnected) {
|
|
2605
2644
|
if (didShowDisconnect()) {
|
|
2606
|
-
success(`Reconnected, synced ${effect.totalCount}
|
|
2645
|
+
success(`Reconnected, synced ${pluralize(effect.totalCount, "file")} (${effect.updatedCount} updated, ${effect.unchangedCount} unchanged)`);
|
|
2607
2646
|
status("Watching for changes...");
|
|
2608
2647
|
}
|
|
2609
2648
|
resetDisconnectState();
|
|
@@ -2613,9 +2652,10 @@ async function executeEffect(effect, context) {
|
|
|
2613
2652
|
const relativeDirectory = relative != null ? relative ? "./" + relative : "." : null;
|
|
2614
2653
|
if (effect.totalCount === 0 && relativeDirectory) if (config.projectDirCreated) success(`Created ${relativeDirectory} folder`);
|
|
2615
2654
|
else success(`Syncing to ${relativeDirectory} folder`);
|
|
2616
|
-
else if (relativeDirectory && config.projectDirCreated) success(`
|
|
2617
|
-
else if (relativeDirectory) success(`Synced into ${relativeDirectory} (${effect.updatedCount}
|
|
2618
|
-
else success(`Synced ${effect.totalCount}
|
|
2655
|
+
else if (relativeDirectory && config.projectDirCreated) success(`Created ${relativeDirectory} (${pluralize(effect.updatedCount, "file")} added)`);
|
|
2656
|
+
else if (relativeDirectory) success(`Synced into ${relativeDirectory} (${pluralize(effect.updatedCount, "file")} updated, ${effect.unchangedCount} unchanged)`);
|
|
2657
|
+
else success(`Synced ${pluralize(effect.totalCount, "file")} (${effect.updatedCount} updated, ${effect.unchangedCount} unchanged)`);
|
|
2658
|
+
if (config.projectDirCreated && config.projectDir) tryGitInit(config.projectDir);
|
|
2619
2659
|
status("Watching for changes...");
|
|
2620
2660
|
return [];
|
|
2621
2661
|
}
|
|
@@ -2677,6 +2717,14 @@ async function start(config) {
|
|
|
2677
2717
|
}
|
|
2678
2718
|
(async () => {
|
|
2679
2719
|
cancelDisconnectMessage();
|
|
2720
|
+
if (syncState.mode !== "disconnected") {
|
|
2721
|
+
if (syncState.socket === client) {
|
|
2722
|
+
debug(`Ignoring duplicate handshake from active socket in ${syncState.mode} mode`);
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
debug(`New handshake received in ${syncState.mode} mode, resetting sync state`);
|
|
2726
|
+
await processEvent({ type: "DISCONNECT" });
|
|
2727
|
+
}
|
|
2680
2728
|
if (!wasRecentlyDisconnected() && !didShowDisconnect()) success(`Connected to ${message.projectName}`);
|
|
2681
2729
|
await processEvent({
|
|
2682
2730
|
type: "HANDSHAKE",
|
|
@@ -2707,7 +2755,7 @@ async function start(config) {
|
|
|
2707
2755
|
event = { type: "REQUEST_FILES" };
|
|
2708
2756
|
break;
|
|
2709
2757
|
case "file-list":
|
|
2710
|
-
debug(`Received file list: ${message.files.length}
|
|
2758
|
+
debug(`Received file list: ${pluralize(message.files.length, "file")}`);
|
|
2711
2759
|
event = {
|
|
2712
2760
|
type: "REMOTE_FILE_LIST",
|
|
2713
2761
|
files: message.files
|
|
@@ -2783,7 +2831,11 @@ async function start(config) {
|
|
|
2783
2831
|
}
|
|
2784
2832
|
})();
|
|
2785
2833
|
});
|
|
2786
|
-
connection.on("disconnect", () => {
|
|
2834
|
+
connection.on("disconnect", (client) => {
|
|
2835
|
+
if (syncState.socket !== client) {
|
|
2836
|
+
debug("[STATE] Ignoring disconnect from stale socket");
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2787
2839
|
scheduleDisconnectMessage(() => {
|
|
2788
2840
|
status("Disconnected, waiting to reconnect...");
|
|
2789
2841
|
});
|