@glassnote/client 2.4.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/userData.js ADDED
@@ -0,0 +1,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
+ function ensureFileExists() {
33
+
34
+ if (!fs.existsSync(userDataDir)) {
35
+ fs.mkdirSync(userDataDir, { recursive: true });
36
+ }
37
+
38
+ if (!fs.existsSync(userDataPath)) {
39
+ let uuid = uuidv4(); // Default to new UUID
40
+ let servers = []; // Default empty servers array
41
+
42
+ // Check if Godot version's data file exists and try to migrate data
43
+ if (fs.existsSync(godotDataPath)) {
44
+ try {
45
+ const godotData = fs.readFileSync(godotDataPath, 'utf8');
46
+ const parsedData = JSON.parse(godotData);
47
+
48
+ // Migrate UUID if available
49
+ if (parsedData.uuid) {
50
+ uuid = parsedData.uuid;
51
+ }
52
+
53
+ // Migrate server value to servers array if available
54
+ if (parsedData.server) {
55
+ servers = [parsedData.server];
56
+ }
57
+ } catch (err) {
58
+ console.error('Error reading Godot data file:', err);
59
+ console.error('Generating new UUID due to error reading Godot data');
60
+ }
61
+ } else {
62
+ console.log('No Godot data file found, generating new UUID:', uuid);
63
+ }
64
+
65
+ const initialData = { uuid: uuid, servers: servers };
66
+ fs.writeFileSync(userDataPath, JSON.stringify(initialData, null, 2));
67
+ userDataCache = initialData;
68
+ return initialData;
69
+ }
70
+
71
+ // Load data into cache if not already loaded
72
+ if (userDataCache === null) {
73
+ userDataCache = readData();
74
+ } else {
75
+ console.log('Cache already loaded, skipping readData()');
76
+ }
77
+
78
+ // Validate that UUID exists and is a valid format
79
+
80
+ if (!userDataCache.uuid || typeof userDataCache.uuid !== 'string' || userDataCache.uuid.trim() === '' || !isValidUUID(userDataCache.uuid)) {
81
+
82
+ const newUUID = uuidv4();
83
+ userDataCache.uuid = newUUID;
84
+ writeData(userDataCache);
85
+ } else {
86
+ console.log('UUID validation PASSED');
87
+ }
88
+
89
+ return userDataCache;
90
+ }
91
+
92
+ function readData() {
93
+
94
+ try {
95
+ const data = fs.readFileSync(userDataPath, 'utf8');
96
+
97
+ const parsedData = JSON.parse(data);
98
+
99
+ // Ensure we always return an object with proper structure
100
+ if (typeof parsedData !== 'object' || parsedData === null) {
101
+ console.error('Invalid data format, returning default structure');
102
+ userDataCache = { uuid: null, servers: [] };
103
+ return userDataCache;
104
+ }
105
+
106
+ // Ensure servers array exists
107
+ if (!Array.isArray(parsedData.servers)) {
108
+ if (typeof parsedData.servers === 'string') {
109
+ parsedData.servers = [parsedData.servers];
110
+ } else {
111
+ parsedData.servers = [];
112
+ }
113
+ } else {
114
+ console.log('servers is already array, length:', parsedData.servers.length);
115
+ }
116
+
117
+ userDataCache = parsedData;
118
+ return userDataCache;
119
+ } catch (err) {
120
+ console.error('Error reading user data:', err.message);
121
+ console.error('Error stack:', err.stack);
122
+ // Return a properly structured object instead of empty
123
+ userDataCache = { uuid: null, servers: [] };
124
+ console.error('Returning error structure:', userDataCache);
125
+ console.error('=== readData() returning (error) ===');
126
+ return userDataCache;
127
+ }
128
+ }
129
+
130
+ function writeData(data) {
131
+
132
+ // Validate data structure before writing
133
+ if (!data || typeof data !== 'object') {
134
+ console.error('writeData() called with invalid data:', data);
135
+ return false;
136
+ }
137
+
138
+ // ALWAYS preserve a valid UUID - never overwrite with invalid/null UUID
139
+ let originalUUID = data.uuid;
140
+ let needsUUIDPreservation = false;
141
+
142
+ // Check if UUID in data is valid
143
+ if (!data.uuid || typeof data.uuid !== 'string' || !isValidUUID(data.uuid)) {
144
+ console.warn('writeData(): UUID in data is invalid:', data.uuid);
145
+ needsUUIDPreservation = true;
146
+ }
147
+
148
+ // If UUID needs preservation, read current file
149
+ if (needsUUIDPreservation) {
150
+ try {
151
+ if (fs.existsSync(userDataPath)) {
152
+ const currentContent = fs.readFileSync(userDataPath, 'utf8');
153
+ const currentData = JSON.parse(currentContent);
154
+ if (currentData.uuid && isValidUUID(currentData.uuid)) {
155
+ data.uuid = currentData.uuid;
156
+ } else {
157
+ console.warn('writeData(): No valid UUID in current file either');
158
+ // If no valid UUID exists anywhere, generate a new one
159
+ if (!data.uuid || !isValidUUID(data.uuid)) {
160
+ const newUUID = uuidv4();
161
+ data.uuid = newUUID;
162
+ }
163
+ }
164
+ } else {
165
+ // File doesn't exist, ensure we have a valid UUID
166
+ if (!data.uuid || !isValidUUID(data.uuid)) {
167
+ const newUUID = uuidv4();
168
+ data.uuid = newUUID;
169
+ }
170
+ }
171
+ } catch (err) {
172
+ console.error('writeData(): Error reading current file:', err.message);
173
+ // On error, ensure we have a valid UUID
174
+ if (!data.uuid || !isValidUUID(data.uuid)) {
175
+ const newUUID = uuidv4();
176
+ console.error('writeData(): Generating new UUID after error:', newUUID);
177
+ data.uuid = newUUID;
178
+ }
179
+ }
180
+ } else {
181
+ console.log('writeData(): UUID in data is valid:', data.uuid);
182
+ }
183
+
184
+ // Create backup before writing
185
+ try {
186
+ if (fs.existsSync(userDataPath)) {
187
+ const backupPath = userDataPath + '.backup';
188
+ fs.copyFileSync(userDataPath, backupPath);
189
+ }
190
+ } catch (err) {
191
+ console.warn('writeData(): Could not create backup:', err.message);
192
+ }
193
+
194
+ try {
195
+ fs.writeFileSync(userDataPath, JSON.stringify(data, null, 2));
196
+ return true;
197
+ } catch (err) {
198
+ console.error('Error writing user data:', err.message);
199
+ console.error('Error stack:', err.stack);
200
+ return false;
201
+ }
202
+ }
203
+
204
+ function get(key, nestedKey) {
205
+ // Handle both get(key) and get(key, nestedKey)
206
+ if (arguments.length === 1) {
207
+ nestedKey = undefined;
208
+ }
209
+
210
+ // Ensure cache is loaded
211
+ if (userDataCache === null) {
212
+ ensureFileExists();
213
+ }
214
+
215
+ if (nestedKey !== undefined) {
216
+ // If nestedKey is provided, get nested value
217
+ if (userDataCache[key] && typeof userDataCache[key] === 'object' && userDataCache[key] !== null) {
218
+ return userDataCache[key][nestedKey];
219
+ }
220
+ return undefined;
221
+ } else {
222
+ // If only key is provided, get top-level value
223
+ return userDataCache[key];
224
+ }
225
+ }
226
+
227
+ function set(key, nestedKey, value) {
228
+
229
+ // Handle both set(key, value) and set(key, nestedKey, value)
230
+ if (arguments.length === 2) {
231
+ // set(key, value) - value is actually the second argument
232
+ value = nestedKey;
233
+ nestedKey = undefined;
234
+ }
235
+
236
+ // BLOCK ATTEMPTS TO OVERWRITE UUID FROM RENDERER
237
+ if (key === 'uuid' && nestedKey === undefined) {
238
+ console.error('SECURITY: Attempt to overwrite UUID blocked!');
239
+ console.error('UUID can only be set by Electron main process, not from renderer.');
240
+
241
+ // Get current UUID to return success but don't actually change it
242
+ if (userDataCache === null) {
243
+ ensureFileExists();
244
+ }
245
+
246
+ // CRITICAL FIX: DO NOT update cache with invalid UUID from renderer
247
+ // Keep the existing UUID in cache to prevent ensureFileExists() from regenerating it
248
+
249
+ // Return true to avoid breaking renderer expectations, but UUID is not actually changed
250
+ return true;
251
+ }
252
+
253
+ // Ensure cache is loaded
254
+ if (userDataCache === null) {
255
+ ensureFileExists();
256
+ }
257
+
258
+ // Validate cache structure
259
+ if (!userDataCache || typeof userDataCache !== 'object') {
260
+ console.error('set(): userDataCache is invalid, reinitializing');
261
+ userDataCache = { uuid: null, servers: [] };
262
+ }
263
+
264
+ // Preserve UUID if it exists but cache structure is incomplete
265
+ if (!userDataCache.uuid || typeof userDataCache.uuid !== 'string') {
266
+ try {
267
+ if (fs.existsSync(userDataPath)) {
268
+ const currentContent = fs.readFileSync(userDataPath, 'utf8');
269
+ const currentData = JSON.parse(currentContent);
270
+ if (currentData.uuid && typeof currentData.uuid === 'string') {
271
+ userDataCache.uuid = currentData.uuid;
272
+ }
273
+ }
274
+ } catch (err) {
275
+ console.error('set(): Error reading file for UUID:', err.message);
276
+ }
277
+ }
278
+
279
+ // Handle case where nestedKey is an object but should be treated as value
280
+ // This happens when: set(key, object, undefined) or set(key, object, null)
281
+ if (nestedKey !== undefined && typeof nestedKey === 'object' &&
282
+ (value === undefined || value === null)) {
283
+ // Swap: value = nestedKey, nestedKey = undefined
284
+ value = nestedKey;
285
+ nestedKey = undefined;
286
+ }
287
+
288
+ // Also handle case where nestedKey is not a string (invalid for nested key)
289
+ if (nestedKey !== undefined && typeof nestedKey !== 'string') {
290
+ // If nestedKey is not a string, it can't be used as an object key
291
+ // Treat it as value instead
292
+ value = nestedKey;
293
+ nestedKey = undefined;
294
+ }
295
+
296
+ if (nestedKey !== undefined) {
297
+ // If nestedKey is provided, set nested value
298
+ // Check if current value is an object, if not, replace it with an object
299
+ if (!userDataCache[key] || typeof userDataCache[key] !== 'object' || Array.isArray(userDataCache[key])) {
300
+ userDataCache[key] = {};
301
+ }
302
+ userDataCache[key][nestedKey] = value;
303
+ } else {
304
+ // If only key is provided, set top-level value
305
+ userDataCache[key] = value;
306
+ }
307
+
308
+ return writeData(userDataCache);
309
+ }
310
+
311
+ function remove(key, nestedKey) {
312
+ // Handle both remove(key) and remove(key, nestedKey)
313
+ if (arguments.length === 1) {
314
+ nestedKey = undefined;
315
+ }
316
+
317
+ // Ensure cache is loaded
318
+ if (userDataCache === null) {
319
+ ensureFileExists();
320
+ }
321
+
322
+ if (nestedKey !== undefined) {
323
+ // If nestedKey is provided, remove nested value
324
+ if (userDataCache[key] && typeof userDataCache[key] === 'object' && userDataCache[key] !== null) {
325
+ delete userDataCache[key][nestedKey];
326
+ // If the object becomes empty, remove the entire key
327
+ if (Object.keys(userDataCache[key]).length === 0) {
328
+ delete userDataCache[key];
329
+ }
330
+ }
331
+ } else {
332
+ // If only key is provided, remove top-level value
333
+ delete userDataCache[key];
334
+ }
335
+
336
+ return writeData(userDataCache);
337
+ }
338
+
339
+ function getUUID() {
340
+
341
+ // If UUID is already in cache and valid, return it immediately
342
+ if (userDataCache && userDataCache.uuid && typeof userDataCache.uuid === 'string' && isValidUUID(userDataCache.uuid)) {
343
+ return userDataCache.uuid;
344
+ }
345
+
346
+ // Otherwise, ensure file exists and get UUID
347
+ const data = ensureFileExists();
348
+
349
+ // Double-check that UUID is valid
350
+ if (!data.uuid || typeof data.uuid !== 'string' || !isValidUUID(data.uuid)) {
351
+ console.error('getUUID(): Invalid UUID returned from ensureFileExists():', data.uuid);
352
+ // Try to read directly from file as last resort
353
+ try {
354
+ if (fs.existsSync(userDataPath)) {
355
+ const fileContent = fs.readFileSync(userDataPath, 'utf8');
356
+ const fileData = JSON.parse(fileContent);
357
+ if (fileData.uuid && isValidUUID(fileData.uuid)) {
358
+ // Update cache
359
+ userDataCache.uuid = fileData.uuid;
360
+ writeData(userDataCache);
361
+ return fileData.uuid;
362
+ }
363
+ }
364
+ } catch (err) {
365
+ console.error('getUUID(): Error reading UUID from file:', err.message);
366
+ }
367
+
368
+ // Generate new UUID as last resort
369
+ const newUUID = uuidv4();
370
+ userDataCache.uuid = newUUID;
371
+ writeData(userDataCache);
372
+ return newUUID;
373
+ }
374
+
375
+ return data.uuid;
376
+ }
377
+
378
+ // Función específica para leer el lock desde el archivo (no del cache)
379
+ function getLockFromFile() {
380
+ try {
381
+ // Leer directamente del archivo, no del cache
382
+ if (!fs.existsSync(userDataPath)) {
383
+ return undefined;
384
+ }
385
+
386
+ const data = fs.readFileSync(userDataPath, 'utf8');
387
+ const parsedData = JSON.parse(data);
388
+
389
+ // Retornar el valor del lock si existe
390
+ return parsedData.lock;
391
+ } catch (err) {
392
+ console.error('Error reading lock from file:', err);
393
+ return undefined;
394
+ }
395
+ }
396
+
397
+ function getUserDataPath() {
398
+ return userDataDir;
399
+ }
400
+
401
+ function getUserDataDir() {
402
+ return userDataDir;
403
+ }
404
+
405
+ // Función auxiliar para validar formato UUID
406
+ function isValidUUID(uuid) {
407
+ if (!uuid || typeof uuid !== 'string') return false;
408
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
409
+ return uuidRegex.test(uuid);
410
+ }
411
+
412
+ // Función para validar y reparar datos de usuario
413
+ function validateAndRepairUserData() {
414
+
415
+ try {
416
+ if (!fs.existsSync(userDataPath)) {
417
+ return ensureFileExists();
418
+ }
419
+
420
+ // Read current file
421
+ const fileContent = fs.readFileSync(userDataPath, 'utf8');
422
+ const fileData = JSON.parse(fileContent);
423
+
424
+ let needsRepair = false;
425
+
426
+ // Validate UUID
427
+ if (!fileData.uuid || typeof fileData.uuid !== 'string' || !isValidUUID(fileData.uuid)) {
428
+ console.warn('Invalid UUID in file, repairing...');
429
+ needsRepair = true;
430
+
431
+ // Try to preserve existing UUID if format is close
432
+ if (fileData.uuid && typeof fileData.uuid === 'string' && fileData.uuid.trim() !== '') {
433
+ // Check if it's a UUID without dashes or with wrong format
434
+ const cleanUUID = fileData.uuid.trim().toLowerCase();
435
+ if (cleanUUID.length === 32 && /^[0-9a-f]{32}$/.test(cleanUUID)) {
436
+ // Convert 32-char hex to UUID format
437
+ fileData.uuid = cleanUUID.replace(/([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})/, '$1-$2-$3-$4-$5');
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
+ };