@glassnote/client 2.4.7 → 2.4.8
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/INSTALACION.md +25 -0
- package/package.json +1 -1
- package/userData.js +437 -492
package/INSTALACION.md
CHANGED
|
@@ -108,6 +108,31 @@ sesión de PowerShell, y la app —que la abre el autoarranque— no lo tendría
|
|
|
108
108
|
auto-actualización no puede funcionar en esa máquina: hay que reinstalar el cliente
|
|
109
109
|
(`irm https://glassnote.intermark.ec/install.ps1 | iex`) o instalar Node.
|
|
110
110
|
|
|
111
|
+
## Identidad del dispositivo
|
|
112
|
+
|
|
113
|
+
El uuid con el que el backend reconoce al equipo vive en su propio archivo, `device-id`,
|
|
114
|
+
al lado de los datos de usuario (`%APPDATA%\glassnote` en Windows, `~/.config/glassnote`
|
|
115
|
+
en Linux, `~/Library/Application Support/glassnote` en macOS). Se escribe una vez y no se
|
|
116
|
+
vuelve a tocar.
|
|
117
|
+
|
|
118
|
+
Antes vivía dentro de `.glassnote`, que se reescribe cada vez que cambia cualquier ajuste.
|
|
119
|
+
Bastaba que ese archivo quedara ilegible —un BOM que le metiera cualquier herramienta de
|
|
120
|
+
Windows, un corte de luz a mitad de la escritura— para que la app generara un uuid nuevo:
|
|
121
|
+
el equipo aparecía como uno distinto en el backend y el anterior quedaba huérfano, sin
|
|
122
|
+
rastro de que eran el mismo.
|
|
123
|
+
|
|
124
|
+
Lo que hace ahora al arrancar, en orden:
|
|
125
|
+
|
|
126
|
+
1. `device-id`, si está;
|
|
127
|
+
2. si no, lo migra del `.glassnote`, de su `.backup` o del archivo de la versión Godot —
|
|
128
|
+
por eso los equipos que ya están instalados conservan su identidad;
|
|
129
|
+
3. solo si no queda nada de dónde sacarlo, genera uno nuevo, y lo dice en el log
|
|
130
|
+
(`Sin identidad previa recuperable`).
|
|
131
|
+
|
|
132
|
+
Un `.glassnote` ilegible ya no se sobrescribe: se aparta como `.glassnote.corrupto-<fecha>`
|
|
133
|
+
y se intenta recuperar la configuración del `.backup`. Las escrituras van a un temporal y
|
|
134
|
+
se renombran, así un cierre a destiempo no deja el archivo a medias.
|
|
135
|
+
|
|
111
136
|
Por `npx` no se actualiza nada: la caché es efímera y npx ya baja la última versión cada
|
|
112
137
|
vez. Tampoco se registra el arranque automático — apuntaría a una carpeta que se limpia
|
|
113
138
|
sola.
|
package/package.json
CHANGED
package/userData.js
CHANGED
|
@@ -1,492 +1,437 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { v4: uuidv4 } = require('uuid');
|
|
4
|
-
|
|
5
|
-
// Logging de diagnóstico para problema del UUID
|
|
6
|
-
|
|
7
|
-
const userDataDir = path.join(
|
|
8
|
-
process.env.APPDATA ||
|
|
9
|
-
(process.platform === 'darwin'
|
|
10
|
-
? path.join(process.env.HOME, 'Library', 'Application Support')
|
|
11
|
-
: path.join(process.env.HOME, '.config')),
|
|
12
|
-
'glassnote'
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
const userDataPath = path.join(userDataDir, '.glassnote');
|
|
16
|
-
|
|
17
|
-
// Path to Godot version's data file
|
|
18
|
-
// Get the base app data directory (parent of userDataDir)
|
|
19
|
-
const baseAppDataDir = path.dirname(userDataDir);
|
|
20
|
-
const godotDataPath = path.join(
|
|
21
|
-
userDataDir,
|
|
22
|
-
'Godot',
|
|
23
|
-
'app_userdata',
|
|
24
|
-
'glassnote',
|
|
25
|
-
'.glassnote'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// In-memory cache for user data
|
|
30
|
-
let userDataCache = null;
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
nestedKey
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
} else {
|
|
439
|
-
// Generate new UUID
|
|
440
|
-
fileData.uuid = uuidv4();
|
|
441
|
-
}
|
|
442
|
-
} else {
|
|
443
|
-
// Generate new UUID
|
|
444
|
-
fileData.uuid = uuidv4();
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Ensure servers array exists
|
|
449
|
-
if (!Array.isArray(fileData.servers)) {
|
|
450
|
-
console.warn('Invalid servers format, repairing...');
|
|
451
|
-
needsRepair = true;
|
|
452
|
-
if (typeof fileData.servers === 'string') {
|
|
453
|
-
fileData.servers = [fileData.servers];
|
|
454
|
-
} else {
|
|
455
|
-
fileData.servers = [];
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Update cache
|
|
460
|
-
userDataCache = fileData;
|
|
461
|
-
|
|
462
|
-
if (needsRepair) {
|
|
463
|
-
writeData(fileData);
|
|
464
|
-
} else {
|
|
465
|
-
console.log('Data validation passed, no repairs needed');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return fileData;
|
|
469
|
-
} catch (err) {
|
|
470
|
-
console.error('Error validating user data:', err.message);
|
|
471
|
-
console.error('Falling back to ensureFileExists()');
|
|
472
|
-
return ensureFileExists();
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// NOT calling ensureFileExists() here anymore - will be called on demand
|
|
477
|
-
// This prevents double initialization and potential UUID regeneration
|
|
478
|
-
|
|
479
|
-
module.exports = {
|
|
480
|
-
ensureFileExists,
|
|
481
|
-
readData,
|
|
482
|
-
writeData,
|
|
483
|
-
get,
|
|
484
|
-
set,
|
|
485
|
-
remove,
|
|
486
|
-
getUUID,
|
|
487
|
-
getLockFromFile,
|
|
488
|
-
getUserDataPath,
|
|
489
|
-
getUserDataDir,
|
|
490
|
-
isValidUUID,
|
|
491
|
-
validateAndRepairUserData,
|
|
492
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { v4: uuidv4 } = require('uuid');
|
|
4
|
+
|
|
5
|
+
// Logging de diagnóstico para problema del UUID
|
|
6
|
+
|
|
7
|
+
const userDataDir = path.join(
|
|
8
|
+
process.env.APPDATA ||
|
|
9
|
+
(process.platform === 'darwin'
|
|
10
|
+
? path.join(process.env.HOME, 'Library', 'Application Support')
|
|
11
|
+
: path.join(process.env.HOME, '.config')),
|
|
12
|
+
'glassnote'
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const userDataPath = path.join(userDataDir, '.glassnote');
|
|
16
|
+
|
|
17
|
+
// Path to Godot version's data file
|
|
18
|
+
// Get the base app data directory (parent of userDataDir)
|
|
19
|
+
const baseAppDataDir = path.dirname(userDataDir);
|
|
20
|
+
const godotDataPath = path.join(
|
|
21
|
+
userDataDir,
|
|
22
|
+
'Godot',
|
|
23
|
+
'app_userdata',
|
|
24
|
+
'glassnote',
|
|
25
|
+
'.glassnote'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// In-memory cache for user data
|
|
30
|
+
let userDataCache = null;
|
|
31
|
+
|
|
32
|
+
// ── Identidad del dispositivo ───────────────────────────────────────────────────────
|
|
33
|
+
//
|
|
34
|
+
// El uuid vive en SU PROPIO archivo (`device-id`), que se escribe una vez y no se vuelve
|
|
35
|
+
// a tocar. Antes vivía dentro de `.glassnote`, que se reescribe cada vez que cambia
|
|
36
|
+
// cualquier ajuste: bastaba que ese archivo quedara ilegible —un BOM que le metiera
|
|
37
|
+
// cualquier herramienta de Windows, un corte de luz a mitad de la escritura— para que la
|
|
38
|
+
// app generara un uuid nuevo y el backend viera un equipo distinto, sin forma de saber
|
|
39
|
+
// que era el mismo. Separarlo convierte «se corrompió la configuración», que es
|
|
40
|
+
// recuperable, en algo que ya no arrastra la identidad.
|
|
41
|
+
const deviceIdPath = path.join(userDataDir, 'device-id');
|
|
42
|
+
const backupPath = userDataPath + '.backup';
|
|
43
|
+
|
|
44
|
+
let deviceIdCache = null;
|
|
45
|
+
|
|
46
|
+
// El BOM son tres bytes (EF BB BF) que Windows antepone al UTF-8 y que JSON.parse no
|
|
47
|
+
// tolera: el archivo se ve perfecto al abrirlo y revienta igual. Se quita antes de parsear.
|
|
48
|
+
function stripBom(text) {
|
|
49
|
+
return typeof text === 'string' && text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Escribir directo sobre el archivo real lo deja truncado si el proceso muere a mitad, y
|
|
53
|
+
// un archivo truncado es justo cómo se llegaba a perder la identidad. Al temporal y
|
|
54
|
+
// rename, que es atómico.
|
|
55
|
+
function writeAtomic(file, content) {
|
|
56
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
57
|
+
fs.writeFileSync(tmp, content);
|
|
58
|
+
fs.renameSync(tmp, file);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// null = el archivo NO existe. Caso legítimo: primer arranque.
|
|
62
|
+
// lanza = existe pero no se pudo leer o parsear.
|
|
63
|
+
// Confundir los dos casos era el bug: un archivo ilegible se trataba como «no hay datos»
|
|
64
|
+
// y de ahí salía un uuid nuevo.
|
|
65
|
+
function loadFile(file) {
|
|
66
|
+
if (!fs.existsSync(file)) return null;
|
|
67
|
+
const parsed = JSON.parse(stripBom(fs.readFileSync(file, 'utf8')));
|
|
68
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
69
|
+
throw new Error('el contenido no es un objeto');
|
|
70
|
+
}
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// El ilegible se aparta con fecha en vez de sobrescribirse: sin esto no queda rastro de
|
|
75
|
+
// qué pasó ni forma de recuperarlo a mano.
|
|
76
|
+
function apartarIlegible(file) {
|
|
77
|
+
try {
|
|
78
|
+
if (!fs.existsSync(file)) return null;
|
|
79
|
+
const destino = `${file}.corrupto-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
80
|
+
fs.renameSync(file, destino);
|
|
81
|
+
console.error(`Archivo ilegible apartado en ${destino}`);
|
|
82
|
+
return destino;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('No se pudo apartar el archivo ilegible:', error.message);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readDeviceId() {
|
|
90
|
+
try {
|
|
91
|
+
const raw = stripBom(fs.readFileSync(deviceIdPath, 'utf8')).trim();
|
|
92
|
+
return isValidUUID(raw) ? raw : null;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeDeviceId(uuid) {
|
|
99
|
+
try {
|
|
100
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
101
|
+
writeAtomic(deviceIdPath, `${uuid}\n`);
|
|
102
|
+
return true;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('No se pudo guardar device-id:', error.message);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// El uuid del equipo, por orden de confianza. Es el ÚNICO sitio del archivo donde se
|
|
110
|
+
// genera uno nuevo: antes había cuatro repartidos y cualquiera podía disparar.
|
|
111
|
+
function deviceUUID() {
|
|
112
|
+
if (deviceIdCache) return deviceIdCache;
|
|
113
|
+
|
|
114
|
+
const guardado = readDeviceId();
|
|
115
|
+
if (guardado) {
|
|
116
|
+
deviceIdCache = guardado;
|
|
117
|
+
return guardado;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Migración de lo que ya está instalado: hasta ahora el uuid vivía en la config, y
|
|
121
|
+
// antes de eso en el archivo de la versión Godot.
|
|
122
|
+
for (const origen of [userDataPath, backupPath, godotDataPath]) {
|
|
123
|
+
let datos = null;
|
|
124
|
+
try {
|
|
125
|
+
datos = loadFile(origen);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
continue; // ilegible: se prueba el siguiente
|
|
128
|
+
}
|
|
129
|
+
if (datos && isValidUUID(datos.uuid)) {
|
|
130
|
+
writeDeviceId(datos.uuid);
|
|
131
|
+
console.log(`device-id creado a partir de ${origen}`);
|
|
132
|
+
deviceIdCache = datos.uuid;
|
|
133
|
+
return datos.uuid;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Primer arranque de verdad, o no quedó nada de dónde sacarlo. Se avisa fuerte: es
|
|
138
|
+
// el único camino por el que un equipo cambia de identidad.
|
|
139
|
+
const nuevo = uuidv4();
|
|
140
|
+
console.warn(`Sin identidad previa recuperable: se genera un uuid nuevo (${nuevo})`);
|
|
141
|
+
writeDeviceId(nuevo);
|
|
142
|
+
deviceIdCache = nuevo;
|
|
143
|
+
return nuevo;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Config de primer arranque. El uuid NO se decide acá: lo pone deviceUUID().
|
|
147
|
+
function datosIniciales() {
|
|
148
|
+
let servers = [];
|
|
149
|
+
try {
|
|
150
|
+
const godot = loadFile(godotDataPath);
|
|
151
|
+
if (godot && godot.server) servers = [godot.server];
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('No se pudo migrar la config de la versión Godot:', error.message);
|
|
154
|
+
}
|
|
155
|
+
return { uuid: null, servers };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function ensureFileExists() {
|
|
159
|
+
if (!fs.existsSync(userDataDir)) {
|
|
160
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// La identidad se resuelve aparte de la configuración, y antes que ella.
|
|
164
|
+
const uuid = deviceUUID();
|
|
165
|
+
|
|
166
|
+
if (userDataCache === null) {
|
|
167
|
+
userDataCache = readData();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (userDataCache.uuid !== uuid) {
|
|
171
|
+
userDataCache.uuid = uuid;
|
|
172
|
+
writeData(userDataCache);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return userDataCache;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function readData() {
|
|
179
|
+
let datos = null;
|
|
180
|
+
try {
|
|
181
|
+
datos = loadFile(userDataPath);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error(`No se pudo leer ${userDataPath}: ${error.message}`);
|
|
184
|
+
// El backup se venía escribiendo desde siempre y no lo leía NADIE: una red
|
|
185
|
+
// tendida y sin conectar. Antes de dar la configuración por perdida, se prueba.
|
|
186
|
+
try {
|
|
187
|
+
datos = loadFile(backupPath);
|
|
188
|
+
if (datos) console.warn('Configuración recuperada del backup.');
|
|
189
|
+
} catch (errorBackup) {
|
|
190
|
+
console.error(`El backup tampoco sirve: ${errorBackup.message}`);
|
|
191
|
+
datos = null;
|
|
192
|
+
}
|
|
193
|
+
apartarIlegible(userDataPath);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (datos === null) datos = datosIniciales();
|
|
197
|
+
|
|
198
|
+
if (!Array.isArray(datos.servers)) {
|
|
199
|
+
datos.servers = typeof datos.servers === 'string' ? [datos.servers] : [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
userDataCache = datos;
|
|
203
|
+
return userDataCache;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function writeData(data) {
|
|
207
|
+
if (!data || typeof data !== 'object') {
|
|
208
|
+
console.error('writeData(): datos inválidos:', data);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// El uuid ya no se negocia acá: manda device-id. La maquinaria que había antes para
|
|
213
|
+
// «preservar el uuid leyendo el archivo actual» no llegaba a correr nunca, porque
|
|
214
|
+
// quien llamaba ya venía con uno recién inventado en la mano.
|
|
215
|
+
data.uuid = deviceUUID();
|
|
216
|
+
|
|
217
|
+
// El backup se hace SOLO desde un archivo que se puede leer. Copiarlo a ciegas
|
|
218
|
+
// destruía la única copia buena justo cuando hacía falta.
|
|
219
|
+
try {
|
|
220
|
+
if (loadFile(userDataPath)) fs.copyFileSync(userDataPath, backupPath);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.warn('El archivo actual no es legible: se conserva el backup anterior.');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
writeAtomic(userDataPath, JSON.stringify(data, null, 2));
|
|
227
|
+
return true;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('Error escribiendo los datos de usuario:', error.message);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function get(key, nestedKey) {
|
|
235
|
+
// Handle both get(key) and get(key, nestedKey)
|
|
236
|
+
if (arguments.length === 1) {
|
|
237
|
+
nestedKey = undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Ensure cache is loaded
|
|
241
|
+
if (userDataCache === null) {
|
|
242
|
+
ensureFileExists();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (nestedKey !== undefined) {
|
|
246
|
+
// If nestedKey is provided, get nested value
|
|
247
|
+
if (userDataCache[key] && typeof userDataCache[key] === 'object' && userDataCache[key] !== null) {
|
|
248
|
+
return userDataCache[key][nestedKey];
|
|
249
|
+
}
|
|
250
|
+
return undefined;
|
|
251
|
+
} else {
|
|
252
|
+
// If only key is provided, get top-level value
|
|
253
|
+
return userDataCache[key];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function set(key, nestedKey, value) {
|
|
258
|
+
|
|
259
|
+
// Handle both set(key, value) and set(key, nestedKey, value)
|
|
260
|
+
if (arguments.length === 2) {
|
|
261
|
+
// set(key, value) - value is actually the second argument
|
|
262
|
+
value = nestedKey;
|
|
263
|
+
nestedKey = undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// BLOCK ATTEMPTS TO OVERWRITE UUID FROM RENDERER
|
|
267
|
+
if (key === 'uuid' && nestedKey === undefined) {
|
|
268
|
+
console.error('SECURITY: Attempt to overwrite UUID blocked!');
|
|
269
|
+
console.error('UUID can only be set by Electron main process, not from renderer.');
|
|
270
|
+
|
|
271
|
+
// Get current UUID to return success but don't actually change it
|
|
272
|
+
if (userDataCache === null) {
|
|
273
|
+
ensureFileExists();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// CRITICAL FIX: DO NOT update cache with invalid UUID from renderer
|
|
277
|
+
// Keep the existing UUID in cache to prevent ensureFileExists() from regenerating it
|
|
278
|
+
|
|
279
|
+
// Return true to avoid breaking renderer expectations, but UUID is not actually changed
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Ensure cache is loaded
|
|
284
|
+
if (userDataCache === null) {
|
|
285
|
+
ensureFileExists();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate cache structure
|
|
289
|
+
if (!userDataCache || typeof userDataCache !== 'object') {
|
|
290
|
+
console.error('set(): userDataCache is invalid, reinitializing');
|
|
291
|
+
userDataCache = { uuid: null, servers: [] };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// El uuid no se rescata acá: writeData() lo fija desde device-id en cada escritura.
|
|
295
|
+
|
|
296
|
+
// Handle case where nestedKey is an object but should be treated as value
|
|
297
|
+
// This happens when: set(key, object, undefined) or set(key, object, null)
|
|
298
|
+
if (nestedKey !== undefined && typeof nestedKey === 'object' &&
|
|
299
|
+
(value === undefined || value === null)) {
|
|
300
|
+
// Swap: value = nestedKey, nestedKey = undefined
|
|
301
|
+
value = nestedKey;
|
|
302
|
+
nestedKey = undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Also handle case where nestedKey is not a string (invalid for nested key)
|
|
306
|
+
if (nestedKey !== undefined && typeof nestedKey !== 'string') {
|
|
307
|
+
// If nestedKey is not a string, it can't be used as an object key
|
|
308
|
+
// Treat it as value instead
|
|
309
|
+
value = nestedKey;
|
|
310
|
+
nestedKey = undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (nestedKey !== undefined) {
|
|
314
|
+
// If nestedKey is provided, set nested value
|
|
315
|
+
// Check if current value is an object, if not, replace it with an object
|
|
316
|
+
if (!userDataCache[key] || typeof userDataCache[key] !== 'object' || Array.isArray(userDataCache[key])) {
|
|
317
|
+
userDataCache[key] = {};
|
|
318
|
+
}
|
|
319
|
+
userDataCache[key][nestedKey] = value;
|
|
320
|
+
} else {
|
|
321
|
+
// If only key is provided, set top-level value
|
|
322
|
+
userDataCache[key] = value;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return writeData(userDataCache);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function remove(key, nestedKey) {
|
|
329
|
+
// Handle both remove(key) and remove(key, nestedKey)
|
|
330
|
+
if (arguments.length === 1) {
|
|
331
|
+
nestedKey = undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Ensure cache is loaded
|
|
335
|
+
if (userDataCache === null) {
|
|
336
|
+
ensureFileExists();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (nestedKey !== undefined) {
|
|
340
|
+
// If nestedKey is provided, remove nested value
|
|
341
|
+
if (userDataCache[key] && typeof userDataCache[key] === 'object' && userDataCache[key] !== null) {
|
|
342
|
+
delete userDataCache[key][nestedKey];
|
|
343
|
+
// If the object becomes empty, remove the entire key
|
|
344
|
+
if (Object.keys(userDataCache[key]).length === 0) {
|
|
345
|
+
delete userDataCache[key];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} else {
|
|
349
|
+
// If only key is provided, remove top-level value
|
|
350
|
+
delete userDataCache[key];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return writeData(userDataCache);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function getUUID() {
|
|
357
|
+
// Una sola fuente: device-id. Antes esto tenía su propia cadena de rescates que
|
|
358
|
+
// terminaba, como todas las demás, generando un uuid nuevo por su cuenta.
|
|
359
|
+
if (userDataCache === null) ensureFileExists();
|
|
360
|
+
return deviceUUID();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Función específica para leer el lock desde el archivo (no del cache)
|
|
364
|
+
function getLockFromFile() {
|
|
365
|
+
try {
|
|
366
|
+
// Leer directamente del archivo, no del cache
|
|
367
|
+
if (!fs.existsSync(userDataPath)) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const parsedData = JSON.parse(stripBom(fs.readFileSync(userDataPath, 'utf8')));
|
|
372
|
+
|
|
373
|
+
// Retornar el valor del lock si existe
|
|
374
|
+
return parsedData.lock;
|
|
375
|
+
} catch (err) {
|
|
376
|
+
console.error('Error reading lock from file:', err);
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getUserDataPath() {
|
|
382
|
+
return userDataDir;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function getUserDataDir() {
|
|
386
|
+
return userDataDir;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Función auxiliar para validar formato UUID
|
|
390
|
+
function isValidUUID(uuid) {
|
|
391
|
+
if (!uuid || typeof uuid !== 'string') return false;
|
|
392
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
393
|
+
return uuidRegex.test(uuid);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Función para validar y reparar datos de usuario
|
|
397
|
+
function validateAndRepairUserData() {
|
|
398
|
+
// Repara la CONFIGURACIÓN. El uuid no se toca acá: lo pone deviceUUID() y writeData
|
|
399
|
+
// lo vuelve a fijar en cada escritura. Antes esta función también intentaba
|
|
400
|
+
// "arreglar" el uuid y acababa inventando uno.
|
|
401
|
+
try {
|
|
402
|
+
const datos = ensureFileExists();
|
|
403
|
+
let reparar = false;
|
|
404
|
+
|
|
405
|
+
if (!Array.isArray(datos.servers)) {
|
|
406
|
+
console.warn('El campo servers no era un array; se repara.');
|
|
407
|
+
datos.servers = typeof datos.servers === 'string' ? [datos.servers] : [];
|
|
408
|
+
reparar = true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (reparar) writeData(datos);
|
|
412
|
+
return datos;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error('Error validando los datos de usuario:', error.message);
|
|
415
|
+
return ensureFileExists();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// NOT calling ensureFileExists() here anymore - will be called on demand
|
|
420
|
+
// This prevents double initialization and potential UUID regeneration
|
|
421
|
+
|
|
422
|
+
module.exports = {
|
|
423
|
+
ensureFileExists,
|
|
424
|
+
readData,
|
|
425
|
+
writeData,
|
|
426
|
+
get,
|
|
427
|
+
set,
|
|
428
|
+
remove,
|
|
429
|
+
getUUID,
|
|
430
|
+
getLockFromFile,
|
|
431
|
+
getUserDataPath,
|
|
432
|
+
getUserDataDir,
|
|
433
|
+
isValidUUID,
|
|
434
|
+
validateAndRepairUserData,
|
|
435
|
+
deviceUUID,
|
|
436
|
+
getDeviceIdPath: () => deviceIdPath,
|
|
437
|
+
};
|