@glassnote/client 2.4.15 → 2.4.16

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
@@ -67,11 +67,18 @@ curl -fsSL https://glassnote.intermark.ec/install.sh | sh -s -- \
67
67
  ```
68
68
 
69
69
  ```powershell
70
- # Windows
70
+ # Windows — con parámetros: `[scriptblock]` es lo que deja pasárselos al script bajado
71
71
  & ([scriptblock]::Create((irm https://glassnote.intermark.ec/install.ps1))) `
72
72
  -OrgId 55 -ApiKey <clave> -DeviceName 'Caja 01' -UserName 'Santiago'
73
73
  ```
74
74
 
75
+ ```powershell
76
+ # Windows — con variables de entorno: `irm | iex` no deja pasar parámetros, pero el
77
+ # cliente lee las variables (quedan puestas en esa ventana de PowerShell)
78
+ $env:GLASSNOTE_ORG_ID='55'; $env:GLASSNOTE_API_KEY='<clave>'; $env:GLASSNOTE_DEVICE_NAME='Caja 01'; $env:GLASSNOTE_USER_NAME='Santiago'
79
+ irm https://glassnote.intermark.ec/install.ps1 | iex
80
+ ```
81
+
75
82
  | bandera | variable de entorno | por defecto |
76
83
  |---|---|---|
77
84
  | `--org-id` | `GLASSNOTE_ORG_ID` | — (obligatoria) |
@@ -84,6 +91,115 @@ curl -fsSL https://glassnote.intermark.ec/install.sh | sh -s -- \
84
91
  Las variables de entorno existen porque un despliegue por GPO o por MDM muchas veces no
85
92
  puede pasar argumentos pero sí poner entorno.
86
93
 
94
+ ### SCCM, Intune y GPO: el cliente es por usuario
95
+
96
+ El ejecutable, los datos, el token y el autoarranque viven en el perfil de quien instala.
97
+ Es el diseño: en GlassNote un dispositivo es un usuario en un equipo. Dos usuarios en la
98
+ misma máquina son dos dispositivos en el tablero, cada uno con su `device-id`, y cada
99
+ cliente corre mientras su usuario tiene la sesión abierta.
100
+
101
+ Estas herramientas suelen correr los scripts como **SYSTEM**, y un cliente instalado así
102
+ quedaría en el perfil de SYSTEM, donde nadie lo ve.
103
+
104
+ **Lo primero es pedirle a la herramienta que corra en la sesión del usuario:**
105
+
106
+ | herramienta | cómo |
107
+ |---|---|
108
+ | SCCM, Application | *Installation behavior: Install for user*, desplegada a una colección de usuarios |
109
+ | SCCM, Package/Program | *Program can run: Only when a user is logged on* y *Run mode: Run with user's rights*; sirve con colecciones de equipos |
110
+ | Intune, Win32 app | *Install behavior: User* |
111
+ | Intune, script de PowerShell | *Run this script using the logged on credentials: Yes* |
112
+ | GPO | script de inicio de sesión en *User Configuration*, no de arranque en *Computer Configuration* |
113
+
114
+ **Cuando no se puede** —*Run Scripts* de SCCM corre siempre como SYSTEM, igual que una
115
+ Application con *Install for system*— el instalador lo detecta y no instala en SYSTEM:
116
+
117
+ 1. busca al usuario con sesión iniciada (el dueño de `explorer.exe`);
118
+ 2. deja el instalador en el `%TEMP%` de ese usuario y lo corre con una tarea programada de
119
+ un solo uso, en su sesión y con sus permisos;
120
+ 3. espera a que termine, muestra su salida y sale con su código;
121
+ 4. borra la tarea y la carpeta.
122
+
123
+ El comando es el mismo en los dos casos. Las variables de entorno y los parámetros llegan
124
+ a la sesión del usuario; la API key viaja como variable de entorno, nunca en una línea de
125
+ comandos, y el script que la lleva se borra al arrancar.
126
+
127
+ ```powershell
128
+ # el mismo script para todo el parque
129
+ $env:GLASSNOTE_ORG_ID = '<id de la organización>'
130
+ $env:GLASSNOTE_API_KEY = '<clave>'
131
+ irm https://glassnote.intermark.ec/install.ps1 | iex
132
+ ```
133
+
134
+ Sin `GLASSNOTE_DEVICE_NAME` ni `GLASSNOTE_USER_NAME`: cada equipo toma su hostname y el
135
+ usuario de la sesión. Fijos en un despliegue masivo, todos los equipos quedan con el mismo
136
+ nombre.
137
+
138
+ Límites del camino por SYSTEM:
139
+
140
+ - **Sin nadie con sesión iniciada no instala** y sale con código 1. En una Application,
141
+ *Only when a user is logged on*.
142
+ - **Instala para el usuario con sesión**, el primero que encuentra. Otro usuario del mismo
143
+ equipo es otro dispositivo y necesita su propia instalación.
144
+ - ***Run Scripts* corta por timeout** (60 s por defecto) y la primera instalación tarda más
145
+ (baja electron). Si corta, la instalación sigue en la sesión del usuario, pero SCCM no
146
+ ve el resultado. Hay que subir el timeout.
147
+ - El `out.log` de la instalación queda tomado por el cliente, que hereda esa salida, hasta
148
+ que se cierra. La carpeta `glassnote-install-*` se limpia en la siguiente instalación.
149
+
150
+ Para desinstalar, también como SYSTEM, el mismo script con `-Uninstall`:
151
+
152
+ ```powershell
153
+ & ([scriptblock]::Create((irm https://glassnote.intermark.ec/install.ps1))) -Uninstall
154
+ ```
155
+
156
+ Cierra el cliente, quita el autoarranque y el PATH, y borra `%USERPROFILE%\.glassnote`.
157
+ **No toca `%APPDATA%\glassnote`**: la identidad del equipo (`device-id`), la configuración
158
+ y el token se conservan, así que al reinstalar es el mismo dispositivo en el tablero. Si el
159
+ node que corre la desinstalación vive dentro de `.glassnote` (el que bajó el instalador),
160
+ esa carpeta la borra un proceso aparte unos segundos después, porque Windows no deja borrar
161
+ un ejecutable en uso.
162
+
163
+ ### Dónde queda cada cosa en Windows
164
+
165
+ | | ruta |
166
+ |---|---|
167
+ | paquete y `electron.exe` | `%USERPROFILE%\.glassnote\npm\node_modules\@glassnote\client` |
168
+ | comando `glassnote` | `%USERPROFILE%\.glassnote\npm\glassnote.cmd` |
169
+ | Node bajado por el instalador, si no había | `%USERPROFILE%\.glassnote\node-v20.18.1-win-x64` |
170
+ | configuración y token | `%APPDATA%\glassnote\.glassnote` |
171
+ | identidad del equipo | `%APPDATA%\glassnote\device-id` |
172
+ | logs: uno por día, se borran a los 30 días | `%APPDATA%\glassnote\logs` |
173
+ | renderer bajado en caliente | `%APPDATA%\glassnote\renderer` |
174
+ | volcados de crash | `%APPDATA%\@glassnote\client\Crashpad` |
175
+ | autoarranque | `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\glassnote.lnk` |
176
+
177
+ ### Auto-actualización
178
+
179
+ Las dos corren como el usuario y sin administrador, así que siguen funcionando aunque la
180
+ instalación la haya lanzado SYSTEM.
181
+
182
+ - **Cliente**: cada hora pregunta a `registry.npmjs.org` por la versión `latest`, o por
183
+ `desiredVersion` si está puesta. Si hay otra, lanza un ayudante con un node de fuera
184
+ del paquete, la app se cierra, el ayudante corre `npm install -g --prefix
185
+ %USERPROFILE%\.glassnote\npm` y la vuelve a abrir. La salida va al log del día. Si
186
+ falla, no reintenta hasta el siguiente chequeo.
187
+ - **Renderer**: a los 10 s de arrancar y después cada hora mira `version_renderer.txt` en el
188
+ bucket de S3 y baja el zip a `%APPDATA%\glassnote\renderer`. No toca el binario.
189
+
190
+ Salidas a internet que necesita el equipo: `registry.npmjs.org` (instalar y actualizar),
191
+ `glassnote.s3.us-east-1.amazonaws.com` (renderer), `nodejs.org` (solo si no hay Node) y
192
+ la API.
193
+
194
+ Sin probar, pero a revisar con el área de sistemas del cliente:
195
+
196
+ - `%APPDATA%` es *Roaming*. Con perfiles móviles, `device-id` y el token viajan con el
197
+ usuario: dos equipos podrían presentarse como el mismo.
198
+ - Una política de AppLocker o WDAC que no deje correr ejecutables desde el perfil del
199
+ usuario bloquea `electron.exe` y `node.exe`.
200
+ - Un proxy: npm no toma el proxy de Windows, necesita `HTTPS_PROXY` o
201
+ `npm_config_proxy`.
202
+
87
203
  ### Otro servidor
88
204
 
89
205
  **El websocket no se elige en el instalador: lo decide la API que atiende el alta.** El
package/bin/glassnote.js CHANGED
@@ -257,7 +257,7 @@ let warnedNoSandbox = false;
257
257
  function launch({ detach = false, extra = [] } = {}) {
258
258
  const binary = electronPath();
259
259
  const args = launchArgs(binary).concat(extra);
260
- const env = Object.assign({}, process.env, {
260
+ const env = Object.assign(pairing.envWithoutPairing(process.env), {
261
261
  // El modo se marca aquí, no se adivina desde el main: sobrevive a symlinks.
262
262
  GLASSNOTE_INSTALL_MODE: mode.detect(),
263
263
  GLASSNOTE_CLI: path.join(__dirname, 'glassnote.js'),
@@ -792,8 +792,63 @@ function cmdUpdate(args) {
792
792
  return 0;
793
793
  }
794
794
 
795
+ // Borra ~/.glassnote. En Windows el node.exe que corre ESTE codigo suele vivir adentro (el
796
+ // instalador lo baja ahi cuando el equipo no tiene Node), y un ejecutable en uso no se puede
797
+ // borrar: rmSync moria con EPERM a mitad y dejaba la carpeta a medias, con el autoarranque
798
+ // ya quitado. Se borra ahora todo lo demas -el paquete incluido, que es lo que mira quien
799
+ // comprueba si esta instalado- y la carpeta de ese node la borra un cmd desprendido cuando
800
+ // este proceso ya termino.
801
+ function removeHomeDir() {
802
+ // Tampoco el cwd puede quedar adentro: Windows no borra el directorio actual de un proceso.
803
+ try {
804
+ process.chdir(os.tmpdir());
805
+ } catch (error) {
806
+ /* se intenta igual */
807
+ }
808
+ const ownExe = path.resolve(process.execPath).toLowerCase();
809
+ let deferred = false;
810
+ let complete = true;
811
+ for (const entry of fs.readdirSync(mode.HOME_DIR)) {
812
+ const entryPath = path.join(mode.HOME_DIR, entry);
813
+ if (IS_WIN && ownExe.startsWith(path.resolve(entryPath).toLowerCase() + path.sep)) {
814
+ deferred = true;
815
+ continue;
816
+ }
817
+ try {
818
+ fs.rmSync(entryPath, { recursive: true, force: true });
819
+ } catch (error) {
820
+ complete = false;
821
+ console.error(`glassnote: no pude borrar ${entryPath}: ${error.message}`);
822
+ }
823
+ }
824
+ if (!complete) return false;
825
+ if (!deferred) {
826
+ fs.rmSync(mode.HOME_DIR, { recursive: true, force: true });
827
+ return true;
828
+ }
829
+ // ping hace de espera porque funciona sin consola; timeout.exe no.
830
+ spawn(process.env.ComSpec || 'cmd.exe', [`/d /s /c "ping -n 4 127.0.0.1 >nul & rmdir /s /q "${mode.HOME_DIR}""`], {
831
+ detached: true,
832
+ stdio: 'ignore',
833
+ windowsHide: true,
834
+ windowsVerbatimArguments: true,
835
+ }).unref();
836
+ step(`${path.dirname(process.execPath)} está en uso: se borra al terminar este proceso`);
837
+ return true;
838
+ }
839
+
795
840
  function cmdUninstall() {
796
841
  log('glassnote — desinstalando');
842
+ // En Windows no se pueden borrar los archivos de un proceso vivo: se cierra el cliente
843
+ // antes de tocar nada, igual que en install. Sin esto quedaba abierto y con la carpeta
844
+ // a medio borrar.
845
+ if (!cerrarClienteWindows()) {
846
+ fail(
847
+ 'el cliente sigue abierto y Windows no deja borrar sus archivos.\n' +
848
+ ' Cerralo desde el ícono de la bandeja (clic derecho → Salir) y volvé a intentar.\n' +
849
+ ' No se tocó nada.'
850
+ );
851
+ }
797
852
  // Se comprueba ANTES de borrar: después los archivos ya no están, pero el proceso que
798
853
  // los tenía abiertos sigue vivo y con su ícono en la bandeja. Decir "Desinstalado" con
799
854
  // la app todavía en pantalla es lo que confunde.
@@ -814,7 +869,7 @@ function cmdUninstall() {
814
869
 
815
870
  if (fs.existsSync(mode.HOME_DIR)) {
816
871
  step(`borrando ${mode.HOME_DIR}`);
817
- fs.rmSync(mode.HOME_DIR, { recursive: true, force: true });
872
+ if (!removeHomeDir()) fail(`no pude borrar todo ${mode.HOME_DIR} (arriba dice qué quedó).`);
818
873
  }
819
874
  log('');
820
875
  if (seguiaAbierto) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassnote/client",
3
- "version": "2.4.15",
3
+ "version": "2.4.16",
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": {
package/pairing.js CHANGED
@@ -318,8 +318,22 @@ function argumentoPosicional(args) {
318
318
  return undefined;
319
319
  }
320
320
 
321
+ // El entorno para la app, sin las variables del alta. La API key de la organizacion solo
322
+ // sirve para darse de alta: heredada, queda semanas en el entorno del cliente y de todo lo
323
+ // que el cliente lance. Windows no distingue mayusculas en los nombres de variable, asi que
324
+ // se comparan sin ellas.
325
+ function envWithoutPairing(env) {
326
+ const pairingNames = OPCIONES.map((o) => o.env.toUpperCase());
327
+ const clean = {};
328
+ for (const key of Object.keys(env || {})) {
329
+ if (!pairingNames.includes(key.toUpperCase())) clean[key] = env[key];
330
+ }
331
+ return clean;
332
+ }
333
+
321
334
  module.exports = {
322
335
  API_URL_POR_DEFECTO,
336
+ envWithoutPairing,
323
337
  BANDERAS_CON_VALOR,
324
338
  argumentoPosicional,
325
339
  OPCIONES,