@glassnote/client 2.4.0 → 2.4.2

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 CHANGED
File without changes
package/README.md CHANGED
File without changes
package/autoStart.js CHANGED
File without changes
package/autoupdate.js CHANGED
File without changes
@@ -5,6 +5,7 @@ const https = require('https');
5
5
  const crypto = require('crypto');
6
6
  const { exec } = require('child_process');
7
7
  const userData = require('./userData');
8
+ const { baselineRendererDir, rendererVersionFrom } = require('./rendererPaths');
8
9
 
9
10
  // El renderer se baja siempre del bucket de PRODUCCIÓN. Antes esto lo decidía un campo
10
11
  // `stage` en package.json que elegía entre este bucket y `glassnotedev`: ese ambiente ya
@@ -23,29 +24,21 @@ class AutoUpdateRenderer {
23
24
  }
24
25
 
25
26
  /**
26
- * Obtiene la versión actual del renderer desde package.json
27
+ * La versión del renderer que está EN USO, que es contra la que hay que comparar el
28
+ * canal. En uso es la que elige loadRendererHtml: la más alta entre lo bajado a
29
+ * userData y el baseline que viaja con el paquete.
30
+ *
31
+ * Antes esto miraba solo userData y, si no había, `<paquete>/renderer/package.json`
32
+ * —una carpeta que dejó de existir cuando el renderer pasó a ser dependencia npm—.
33
+ * Devolvía 0.0.0, así que en cada instalación nueva cualquier versión del canal le
34
+ * ganaba: bajaba el zip de S3 aunque fuera MÁS VIEJO que el baseline, y después el
35
+ * loader se quedaba con el baseline igual: 1.1 MB tirados y una versión "actual"
36
+ * que no era la que se estaba usando.
27
37
  */
28
38
  getCurrentRendererVersion() {
29
- try {
30
- // Primero verificar en userData (actualizaciones)
31
- const userDataRendererPath = path.join(userData.getUserDataDir(), 'renderer', 'package.json');
32
- if (fs.existsSync(userDataRendererPath)) {
33
- const packageJson = JSON.parse(fs.readFileSync(userDataRendererPath, 'utf8'));
34
- return packageJson.version;
35
- }
36
-
37
- // Si no existe en userData, verificar en la aplicación (versión por defecto)
38
- const appRendererPath = path.join(__dirname, 'renderer', 'package.json');
39
- if (fs.existsSync(appRendererPath)) {
40
- const packageJson = JSON.parse(fs.readFileSync(appRendererPath, 'utf8'));
41
- return packageJson.version;
42
- }
43
-
44
- return '0.0.0'; // Versión por defecto si no existe
45
- } catch (error) {
46
- console.error('Error reading renderer version:', error);
47
- return '0.0.0';
48
- }
39
+ const enUserData = rendererVersionFrom(path.join(userData.getUserDataDir(), 'renderer'));
40
+ const enBaseline = rendererVersionFrom(baselineRendererDir());
41
+ return this.compareVersions(enUserData, enBaseline) >= 0 ? enUserData : enBaseline;
49
42
  }
50
43
 
51
44
  /**
package/bin/glassnote.js CHANGED
@@ -32,6 +32,48 @@ function npmEnv() {
32
32
  });
33
33
  }
34
34
 
35
+ // userData tira de `uuid`, y este CLI corre también donde el árbol de dependencias no
36
+ // está (un tarball recién desempacado, un install a medias). Por eso se carga tarde y
37
+ // tolerando el fallo: si no se puede dejar constancia, el peor caso es que la app
38
+ // reintente en el chequeo siguiente. Romper el CLI —que es quien instala— sería peor.
39
+ function conUserData(hacer) {
40
+ try {
41
+ return hacer(require('../userData'));
42
+ } catch (error) {
43
+ return undefined;
44
+ }
45
+ }
46
+
47
+ // La app mira esto antes de reintentar: sin memoria del fallo, un npm que falla siempre
48
+ // deja al cliente cerrándose y abriéndose cada veinte segundos (falla, se relanza,
49
+ // chequea a los 5 segundos, vuelve a fallar).
50
+ function recordarFalloDeUpdate(version, motivo) {
51
+ conUserData((userData) => userData.set('failedUpdate', { version, at: Date.now(), motivo }));
52
+ }
53
+
54
+ function olvidarFalloDeUpdate() {
55
+ conUserData((userData) => {
56
+ if (userData.get('failedUpdate')) userData.remove('failedUpdate');
57
+ });
58
+ }
59
+
60
+ // ¿Hay un cliente vivo? Levanta su servidor local en 48984 (localserver.js), así que su
61
+ // /uuid es la señal más barata. Node no tiene HTTP síncrono y el CLI sí lo es, así que la
62
+ // consulta va en un proceso aparte.
63
+ function clienteEnMarcha() {
64
+ const sonda =
65
+ "const http=require('http');" +
66
+ "const r=http.get('http://127.0.0.1:48984/uuid',(res)=>{res.resume();process.exit(res.statusCode===200?0:1)});" +
67
+ "r.on('error',()=>process.exit(1));" +
68
+ "r.setTimeout(700,()=>{r.destroy();process.exit(1)});";
69
+ const res = spawnSync(process.execPath, ['-e', sonda], {
70
+ env: Object.assign({}, process.env, { ELECTRON_RUN_AS_NODE: '1' }),
71
+ stdio: 'ignore',
72
+ timeout: 4000,
73
+ });
74
+ return res.status === 0;
75
+ }
76
+
35
77
  // Sueño síncrono sin dependencias: el ayudante de actualización tiene que esperar a que
36
78
  // la app termine de cerrarse antes de que npm le reemplace los archivos.
37
79
  function sleepSync(ms) {
@@ -346,6 +388,7 @@ function cmdUpdate(args) {
346
388
  env: npmEnv(),
347
389
  });
348
390
  if (res.status !== 0) {
391
+ recordarFalloDeUpdate(version, `npm salió con ${res.status}`);
349
392
  if (relaunch) {
350
393
  // Falló la actualización: al menos el cliente vuelve. Quedarse sin app por
351
394
  // un npm que no pudo con la red es peor que seguir en la versión vieja.
@@ -355,6 +398,7 @@ function cmdUpdate(args) {
355
398
  fail('npm no pudo actualizar el paquete.');
356
399
  }
357
400
 
401
+ olvidarFalloDeUpdate();
358
402
  log(`Actualizado a v${installedVersion()}.`);
359
403
  if (relaunch) {
360
404
  launch({ detach: true });
@@ -366,6 +410,10 @@ function cmdUpdate(args) {
366
410
 
367
411
  function cmdUninstall() {
368
412
  log('glassnote — desinstalando');
413
+ // Se comprueba ANTES de borrar: después los archivos ya no están, pero el proceso que
414
+ // los tenía abiertos sigue vivo y con su ícono en la bandeja. Decir "Desinstalado" con
415
+ // la app todavía en pantalla es lo que confunde.
416
+ const seguiaAbierto = clienteEnMarcha();
369
417
  step('quitando el autoarranque');
370
418
  removeAutoStart();
371
419
 
@@ -383,6 +431,12 @@ function cmdUninstall() {
383
431
  fs.rmSync(mode.HOME_DIR, { recursive: true, force: true });
384
432
  }
385
433
  log('');
434
+ if (seguiaAbierto) {
435
+ log('OJO: el cliente sigue abierto. Sus archivos ya no están, pero el proceso vive');
436
+ log('hasta que lo cierres desde el ícono de la bandeja (Salir). Después no vuelve a');
437
+ log('abrirse: el autoarranque ya no está.');
438
+ log('');
439
+ }
386
440
  log('Desinstalado. Tus datos y notas siguen donde estaban:');
387
441
  log(` ${userDataDir()}`);
388
442
  log('Borralos a mano si también querés perderlos.');
package/images/demo01.png CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/images/icon.ico CHANGED
File without changes
package/images/icon.png CHANGED
File without changes
package/images/splash.svg CHANGED
File without changes
package/localserver.js CHANGED
File without changes
package/logUtilities.js CHANGED
File without changes
package/main.js CHANGED
@@ -17,6 +17,7 @@ const AutoUpdate = require('./autoupdate');
17
17
  const NpmUpdate = require('./npmUpdate');
18
18
  const npmMode = require('./npmMode');
19
19
  const AutoUpdateRenderer = require('./autoupdate_renderer');
20
+ const { baselineRendererDir, rendererVersionFrom: getRendererVersionFromPath } = require('./rendererPaths');
20
21
  const { createTailUtility } = require('./logUtilities');
21
22
 
22
23
  const logRenderer = packageJson.logRenderer !== undefined ? packageJson.logRenderer : true;
@@ -556,20 +557,7 @@ process.on('unhandledRejection', (reason, promise) => {
556
557
  *
557
558
  * Gana la version mas alta, sin importar de donde venga; empate, gana userData.
558
559
  */
559
- // De donde sale el baseline. En una instalacion es la dependencia @glassnote/renderer,
560
- // que npm resuelve sola. Pero si hay un clon del renderer al lado —el flujo de quien lo
561
- // desarrolla, con `npm run watch-renderer`— ese gana: si no, editarlo no se veria nunca,
562
- // tapado por la version publicada.
563
- function baselineRendererDir() {
564
- const clonLocal = path.join(__dirname, 'glassnote-renderer', 'dist');
565
- if (fs.existsSync(clonLocal)) return clonLocal;
566
- try {
567
- return path.join(path.dirname(require.resolve('@glassnote/renderer/package.json')), 'dist');
568
- } catch (error) {
569
- // Sin dependencia ni clon solo queda userData; si tampoco esta, mas abajo se avisa.
570
- return clonLocal;
571
- }
572
- }
560
+ // baselineRendererDir vive en rendererPaths.js: lo comparte con el updater del renderer.
573
561
 
574
562
  function loadRendererHtml(window, uuid) {
575
563
  // Definir las dos fuentes a considerar con archivos HTML alternativos
@@ -695,38 +683,7 @@ function compareVersions(version1, version2) {
695
683
  return 0; // Las versiones son iguales
696
684
  }
697
685
 
698
- /**
699
- * Obtiene la versión del renderer desde el package.json en el directorio especificado
700
- * Si el directorio es un dist/ sin package.json, se mira el del paquete que lo contiene.
701
- */
702
- function getRendererVersionFromPath(rendererPath) {
703
- try {
704
- // Primero intentar en el directorio especificado
705
- const packageJsonPath = path.join(rendererPath, 'package.json');
706
- if (fs.existsSync(packageJsonPath)) {
707
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
708
- return packageJson.version || '0.0.0';
709
- }
710
-
711
- // Un dist/ sin package.json propio: la version la tiene el paquete de arriba.
712
- if (path.basename(rendererPath) === 'dist') {
713
- const parentDir = path.join(rendererPath, '..'); // Subir un nivel
714
- const parentPackageJsonPath = path.join(parentDir, 'package.json');
715
- if (fs.existsSync(parentPackageJsonPath)) {
716
- const packageJson = JSON.parse(fs.readFileSync(parentPackageJsonPath, 'utf8'));
717
- return packageJson.version || '0.0.0';
718
- }
719
- }
720
- } catch (error) {
721
- console.error(
722
- 'Error reading renderer version from',
723
- rendererPath,
724
- ':',
725
- error
726
- );
727
- }
728
- return '0.0.0'; // Versión por defecto si no existe o hay error
729
- }
686
+ // getRendererVersionFromPath = rendererVersionFrom de rendererPaths.js (ver el require de arriba).
730
687
 
731
688
  /**
732
689
  * Recarga el main window después de una actualización del renderer
package/npmMode.js CHANGED
File without changes
package/npmUpdate.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // desprendido, la app se cierra, el ayudante actualiza y la vuelve a abrir. Igual en las
9
9
  // tres plataformas, así no hay una rama por sistema operativo que solo se pruebe en uno.
10
10
  const https = require('https');
11
+ const fs = require('fs');
11
12
  const path = require('path');
12
13
  const { spawn } = require('child_process');
13
14
 
@@ -90,18 +91,38 @@ class NpmUpdate {
90
91
  const meta = await this.getJson(`${REGISTRY}/${npmMode.PKG_NAME}/${target}`);
91
92
  if (!meta || !meta.version) return;
92
93
 
93
- if (isLatest) {
94
- if (compareVersions(this.currentVersion, meta.version) < 0) {
95
- this.applyUpdate(meta.version);
96
- }
97
- } else if (this.currentVersion !== meta.version) {
98
- this.applyUpdate(meta.version);
99
- }
94
+ const objetivo = meta.version;
95
+ const hayQueActualizar = isLatest
96
+ ? compareVersions(this.currentVersion, objetivo) < 0
97
+ : this.currentVersion !== objetivo;
98
+ if (!hayQueActualizar) return;
99
+ if (this.fallidoHacePoco(objetivo)) return;
100
+ this.applyUpdate(objetivo);
100
101
  } catch (error) {
101
102
  console.error('[update] error comprobando actualizaciones:', error.message);
102
103
  }
103
104
  }
104
105
 
106
+ // Una actualización que falló NO se reintenta enseguida. cmdUpdate relanza la app a
107
+ // propósito cuando npm falla —quedarse sin cliente es peor que quedarse en la versión
108
+ // vieja—, pero el chequeo vuelve a los 5 segundos de arrancar. Con un fallo que
109
+ // persiste (sin red, el registry caído, una dependencia que no resuelve) eso es la app
110
+ // cerrándose y abriéndose cada veinte segundos, para siempre y sin decir por qué. Se
111
+ // recuerda la versión que falló y se espera al chequeo normal.
112
+ fallidoHacePoco(version) {
113
+ try {
114
+ const fallo = userData.get('failedUpdate');
115
+ if (!fallo || fallo.version !== version) return false;
116
+ const desde = Date.now() - (fallo.at || 0);
117
+ if (desde >= CHECK_INTERVAL_MS) return false;
118
+ const minutos = Math.max(1, Math.round((CHECK_INTERVAL_MS - desde) / 60000));
119
+ console.log(`[update] ${version} ya falló; no se reintenta hasta dentro de ~${minutos} min`);
120
+ return true;
121
+ } catch (error) {
122
+ return false;
123
+ }
124
+ }
125
+
105
126
  applyUpdate(version) {
106
127
  if (this.updating) return;
107
128
  this.updating = true;
@@ -117,11 +138,22 @@ class NpmUpdate {
117
138
  PATH: `${npmMode.BIN_DIR}${path.delimiter}${process.env.PATH || ''}`,
118
139
  });
119
140
 
141
+ // El ayudante corre desprendido y su salida no la veía nadie: cuando npm fallaba,
142
+ // el único síntoma era la app reiniciándose sola. Va a un archivo al lado de los
143
+ // datos de usuario, que es donde alguien lo va a buscar.
144
+ let salida = 'ignore';
145
+ try {
146
+ fs.mkdirSync(userData.getUserDataDir(), { recursive: true });
147
+ salida = fs.openSync(path.join(userData.getUserDataDir(), 'update.log'), 'a');
148
+ } catch (error) {
149
+ console.error('[update] no se pudo abrir update.log:', error.message);
150
+ }
151
+
120
152
  try {
121
153
  const child = spawn(
122
154
  process.execPath,
123
155
  [cli, 'update', version, '--relaunch', '--wait', '4000'],
124
- { detached: true, stdio: 'ignore', env }
156
+ { detached: true, stdio: ['ignore', salida, salida], env }
125
157
  );
126
158
  child.unref();
127
159
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassnote/client",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "GlassNote — cliente de escritorio de notas superpuestas. Se instala con npx, sin instaladores nativos ni firmas.",
5
5
  "main": "main.js",
6
6
  "publishConfig": {
@@ -29,6 +29,7 @@
29
29
  "autoStart.js",
30
30
  "autoupdate.js",
31
31
  "autoupdate_renderer.js",
32
+ "rendererPaths.js",
32
33
  "npmMode.js",
33
34
  "npmUpdate.js",
34
35
  "localserver.js",
package/preload.js CHANGED
File without changes
@@ -0,0 +1,62 @@
1
+ // De dónde sale el renderer y qué versión tiene. En un solo sitio a propósito.
2
+ //
3
+ // Esto vivía duplicado en main.js y en autoupdate_renderer.js, y las dos copias se
4
+ // fueron separando: la del updater seguía mirando `<paquete>/renderer/`, una carpeta que
5
+ // dejó de existir cuando el renderer pasó a ser la dependencia @glassnote/renderer. Como
6
+ // no la encontraba devolvía 0.0.0, cualquier versión del canal le ganaba, y cada
7
+ // instalación nueva bajaba el zip de S3 —incluso más viejo que el baseline— para después
8
+ // no usarlo. Un bug silencioso: se ve como 1.1 MB de tráfico que nadie pidió.
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // El baseline es la dependencia @glassnote/renderer, que npm resuelve sola. Pero si hay
15
+ // un clon del renderer al lado —el flujo de quien lo desarrolla, con `npm run
16
+ // watch-renderer`— ese gana: si no, editarlo no se vería nunca, tapado por la versión
17
+ // publicada.
18
+ function baselineRendererDir() {
19
+ const clonLocal = path.join(__dirname, 'glassnote-renderer', 'dist');
20
+ if (fs.existsSync(clonLocal)) return clonLocal;
21
+ try {
22
+ return path.join(path.dirname(require.resolve('@glassnote/renderer/package.json')), 'dist');
23
+ } catch (error) {
24
+ // Sin dependencia ni clon solo queda userData; quien llama avisa si tampoco está.
25
+ return clonLocal;
26
+ }
27
+ }
28
+
29
+ // La versión de un directorio de renderer. El dist/ publicado trae su propio
30
+ // package.json; si algún día no lo trajera, la versión la tiene el paquete de arriba.
31
+ function rendererVersionFrom(rendererPath) {
32
+ try {
33
+ const propio = path.join(rendererPath, 'package.json');
34
+ if (fs.existsSync(propio)) {
35
+ return JSON.parse(fs.readFileSync(propio, 'utf8')).version || '0.0.0';
36
+ }
37
+ if (path.basename(rendererPath) === 'dist') {
38
+ const delPadre = path.join(rendererPath, '..', 'package.json');
39
+ if (fs.existsSync(delPadre)) {
40
+ return JSON.parse(fs.readFileSync(delPadre, 'utf8')).version || '0.0.0';
41
+ }
42
+ }
43
+ } catch (error) {
44
+ console.error('Error reading renderer version from', rendererPath, ':', error);
45
+ }
46
+ return '0.0.0'; // No hay renderer ahí, o no se pudo leer.
47
+ }
48
+
49
+ // Compara dos versiones semánticas: 1 si a es mayor, -1 si b lo es, 0 si empatan.
50
+ function compareRendererVersions(a, b) {
51
+ const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
52
+ const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
53
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
54
+ const va = i < pa.length ? pa[i] : 0;
55
+ const vb = i < pb.length ? pb[i] : 0;
56
+ if (va > vb) return 1;
57
+ if (va < vb) return -1;
58
+ }
59
+ return 0;
60
+ }
61
+
62
+ module.exports = { baselineRendererDir, rendererVersionFrom, compareRendererVersions };
package/userData.js CHANGED
File without changes