@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/INSTALACION.md +258 -0
- package/README.md +228 -0
- package/autoStart.js +608 -0
- package/autoupdate.js +473 -0
- package/autoupdate_renderer.js +371 -0
- package/bin/glassnote.js +523 -0
- package/images/demo01.png +0 -0
- package/images/dmg-background.png +0 -0
- package/images/dmg-background.svg +37628 -0
- package/images/icon-16.png +0 -0
- package/images/icon-24.png +0 -0
- package/images/icon-512.png +0 -0
- package/images/icon.ico +0 -0
- package/images/icon.png +0 -0
- package/images/splash.svg +61 -0
- package/localserver.js +297 -0
- package/logUtilities.js +128 -0
- package/main.js +745 -0
- package/npmMode.js +139 -0
- package/npmUpdate.js +152 -0
- package/package.json +141 -0
- package/preload.js +148 -0
- package/userData.js +492 -0
package/npmMode.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Detección del modo de instalación y rutas del instalador npm/npx.
|
|
2
|
+
//
|
|
3
|
+
// Este archivo lo cargan DOS procesos distintos: el CLI (`bin/glassnote.js`, node puro)
|
|
4
|
+
// y el main de electron. Por eso no puede requerir 'electron' ni nada de electron.
|
|
5
|
+
//
|
|
6
|
+
// Modos:
|
|
7
|
+
// 'packaged' instalador nativo (NSIS/DMG) — la vía vieja, en deprecación
|
|
8
|
+
// 'npm-global' instalado con `glassnote install` bajo ~/.glassnote (la vía oficial)
|
|
9
|
+
// 'npx' corriendo desde la caché de npx, sin instalar: efímero
|
|
10
|
+
// 'source' repo clonado, `npm start` de toda la vida
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
const PKG_NAME = '@glassnote/client';
|
|
16
|
+
// El comando NO se llama como el paquete: npm crea el shim con el nombre del bin, así
|
|
17
|
+
// que se instala con 'npx @glassnote/client' y lo que queda en el PATH es 'glassnote'.
|
|
18
|
+
// Todo lo que sea una ruta o un enlace usa BIN_NAME; lo que hable con npm, PKG_NAME.
|
|
19
|
+
const BIN_NAME = 'glassnote';
|
|
20
|
+
const PACKAGE_ROOT = __dirname;
|
|
21
|
+
|
|
22
|
+
// Todo vive bajo un solo directorio: desinstalar es borrarlo y quitar la línea del rc.
|
|
23
|
+
const HOME_DIR = process.env.GLASSNOTE_HOME || path.join(os.homedir(), '.glassnote');
|
|
24
|
+
const NPM_PREFIX = path.join(HOME_DIR, 'npm');
|
|
25
|
+
|
|
26
|
+
// En Windows npm deja los shims (.cmd/.ps1) en la RAÍZ del prefix, no en prefix/bin.
|
|
27
|
+
// En Linux/macOS usamos un bin propio con symlinks: es LO ÚNICO que se mete al PATH,
|
|
28
|
+
// así la línea del rc no cambia aunque cambie lo de dentro.
|
|
29
|
+
const BIN_DIR = process.platform === 'win32' ? NPM_PREFIX : path.join(HOME_DIR, 'bin');
|
|
30
|
+
|
|
31
|
+
// Dónde npm deja el paquete al instalarlo con --prefix (difiere por plataforma).
|
|
32
|
+
const INSTALLED_PACKAGE_DIR =
|
|
33
|
+
process.platform === 'win32'
|
|
34
|
+
? path.join(NPM_PREFIX, 'node_modules', PKG_NAME)
|
|
35
|
+
: path.join(NPM_PREFIX, 'lib', 'node_modules', PKG_NAME);
|
|
36
|
+
|
|
37
|
+
function realpath(p) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.realpathSync(p);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return p;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isInside(child, parent) {
|
|
46
|
+
const rel = path.relative(realpath(parent), realpath(child));
|
|
47
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detect() {
|
|
51
|
+
// El CLI marca el modo al lanzar electron: es la señal más fiable porque sobrevive
|
|
52
|
+
// a symlinks y a que npm mueva el paquete de sitio.
|
|
53
|
+
if (process.env.GLASSNOTE_INSTALL_MODE) return process.env.GLASSNOTE_INSTALL_MODE;
|
|
54
|
+
|
|
55
|
+
// Los builds nativos van dentro de resources/app(.asar); nunca en node_modules.
|
|
56
|
+
const segments = PACKAGE_ROOT.split(path.sep);
|
|
57
|
+
if (segments.includes('app.asar') || segments.includes('resources')) return 'packaged';
|
|
58
|
+
if (!segments.includes('node_modules')) return 'source';
|
|
59
|
+
// La caché de npx: efímera, no tiene sentido auto-actualizarse ahí.
|
|
60
|
+
if (segments.includes('_npx') || PACKAGE_ROOT.includes('.npm/_npx')) return 'npx';
|
|
61
|
+
if (isInside(PACKAGE_ROOT, NPM_PREFIX)) return 'npm-global';
|
|
62
|
+
return 'npm-global';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── El sandbox de Chromium en Linux ────────────────────────────────────────────────
|
|
66
|
+
//
|
|
67
|
+
// Instalar sin root tiene un precio en Linux: `chrome-sandbox` tiene que ser setuid root
|
|
68
|
+
// y npm no puede dejarlo así. Chromium entonces ABORTA al arrancar (no arranca degradado,
|
|
69
|
+
// se muere), y ese es exactamente el fallo que se ve al correr el cliente por npx.
|
|
70
|
+
//
|
|
71
|
+
// Orden de preferencia, de más a menos seguro:
|
|
72
|
+
// 1. chrome-sandbox setuid root → nada que añadir (lo pone `glassnote fix-sandbox`)
|
|
73
|
+
// 2. namespaces de usuario sin privilegios → --disable-setuid-sandbox (sigue habiendo
|
|
74
|
+
// sandbox, solo que el de namespaces)
|
|
75
|
+
// 3. ni eso → --no-sandbox, que es correr sin sandbox y hay que decirlo
|
|
76
|
+
//
|
|
77
|
+
// Ubuntu 24.04+ cae en el caso 3 aunque el kernel soporte namespaces: AppArmor los
|
|
78
|
+
// restringe a binarios con perfil (apparmor_restrict_unprivileged_userns=1).
|
|
79
|
+
function readProc(file) {
|
|
80
|
+
try {
|
|
81
|
+
return fs.readFileSync(file, 'utf8').trim();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function userNamespacesUsable() {
|
|
88
|
+
if (readProc('/proc/sys/kernel/unprivileged_userns_clone') === '0') return false;
|
|
89
|
+
if (readProc('/proc/sys/user/max_user_namespaces') === '0') return false;
|
|
90
|
+
if (readProc('/proc/sys/kernel/apparmor_restrict_unprivileged_userns') === '1') return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function chromeSandboxPath(electronBinary) {
|
|
95
|
+
return path.join(path.dirname(electronBinary), 'chrome-sandbox');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sandboxIsSetuidRoot(electronBinary) {
|
|
99
|
+
try {
|
|
100
|
+
const st = fs.statSync(chromeSandboxPath(electronBinary));
|
|
101
|
+
return st.uid === 0 && (st.mode & 0o4000) !== 0;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Devuelve las banderas con las que hay que arrancar electron en esta máquina.
|
|
108
|
+
function sandboxFlags(electronBinary) {
|
|
109
|
+
if (process.env.GLASSNOTE_NO_SANDBOX === '1') return ['--no-sandbox'];
|
|
110
|
+
if (process.platform !== 'linux') return [];
|
|
111
|
+
// Como root no hay sandbox de usuario posible.
|
|
112
|
+
if (typeof process.getuid === 'function' && process.getuid() === 0) return ['--no-sandbox'];
|
|
113
|
+
if (sandboxIsSetuidRoot(electronBinary)) return [];
|
|
114
|
+
if (userNamespacesUsable()) return ['--disable-setuid-sandbox'];
|
|
115
|
+
return ['--no-sandbox'];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const isNpmMode = () => detect() === 'npm-global' || detect() === 'npx';
|
|
119
|
+
const isEphemeral = () => detect() === 'npx';
|
|
120
|
+
const isGlobalInstall = () => detect() === 'npm-global';
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
PKG_NAME,
|
|
124
|
+
BIN_NAME,
|
|
125
|
+
PACKAGE_ROOT,
|
|
126
|
+
HOME_DIR,
|
|
127
|
+
NPM_PREFIX,
|
|
128
|
+
BIN_DIR,
|
|
129
|
+
INSTALLED_PACKAGE_DIR,
|
|
130
|
+
detect,
|
|
131
|
+
sandboxFlags,
|
|
132
|
+
sandboxIsSetuidRoot,
|
|
133
|
+
chromeSandboxPath,
|
|
134
|
+
userNamespacesUsable,
|
|
135
|
+
isNpmMode,
|
|
136
|
+
isEphemeral,
|
|
137
|
+
isGlobalInstall,
|
|
138
|
+
isInside,
|
|
139
|
+
};
|
package/npmUpdate.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Auto-actualización cuando el cliente se instaló por npm (`glassnote install`).
|
|
2
|
+
//
|
|
3
|
+
// Sustituye a autoupdate.js — que baja un .exe/.dmg de S3 y lo ejecuta — por lo único
|
|
4
|
+
// que hace falta aquí: `npm install -g glassnote@<version>` contra el registry.
|
|
5
|
+
//
|
|
6
|
+
// El npm NO se corre desde dentro de la app en marcha: en Windows el electron.exe que
|
|
7
|
+
// se está ejecutando está bloqueado y npm no puede reemplazarlo. Se lanza un ayudante
|
|
8
|
+
// desprendido, la app se cierra, el ayudante actualiza y la vuelve a abrir. Igual en las
|
|
9
|
+
// tres plataformas, así no hay una rama por sistema operativo que solo se pruebe en uno.
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { spawn } = require('child_process');
|
|
13
|
+
|
|
14
|
+
const userData = require('./userData');
|
|
15
|
+
const npmMode = require('./npmMode');
|
|
16
|
+
const packageJson = require('./package.json');
|
|
17
|
+
|
|
18
|
+
const REGISTRY = process.env.GLASSNOTE_REGISTRY || 'https://registry.npmjs.org';
|
|
19
|
+
const CHECK_DELAY_MS = 5000;
|
|
20
|
+
const CHECK_INTERVAL_MS = 3 * 60 * 60 * 1000; // cada 3 horas, como el updater viejo
|
|
21
|
+
|
|
22
|
+
class NpmUpdate {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.currentVersion = packageJson.version;
|
|
25
|
+
this.updateInterval = null;
|
|
26
|
+
this.updating = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
start() {
|
|
30
|
+
// Por npx no se actualiza nada: la caché es efímera y npx ya baja la última
|
|
31
|
+
// versión en cada corrida. Actualizar ahí sería instalar por la espalda algo
|
|
32
|
+
// que el usuario pidió explícitamente NO instalar.
|
|
33
|
+
if (!npmMode.isGlobalInstall()) {
|
|
34
|
+
console.log(`[update] modo ${npmMode.detect()}: auto-actualización desactivada`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setTimeout(() => this.checkForUpdates(), CHECK_DELAY_MS);
|
|
39
|
+
this.updateInterval = setInterval(() => this.checkForUpdates(), CHECK_INTERVAL_MS);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop() {
|
|
43
|
+
if (this.updateInterval) {
|
|
44
|
+
clearInterval(this.updateInterval);
|
|
45
|
+
this.updateInterval = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getJson(url) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const req = https.get(url, { headers: { accept: 'application/json' } }, (res) => {
|
|
52
|
+
if (res.statusCode !== 200) {
|
|
53
|
+
res.resume();
|
|
54
|
+
console.error(`[update] HTTP ${res.statusCode} en ${url}`);
|
|
55
|
+
return resolve(null);
|
|
56
|
+
}
|
|
57
|
+
let body = '';
|
|
58
|
+
res.setEncoding('utf8');
|
|
59
|
+
res.on('data', (chunk) => (body += chunk));
|
|
60
|
+
res.on('end', () => {
|
|
61
|
+
try {
|
|
62
|
+
resolve(JSON.parse(body));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('[update] respuesta del registry ilegible:', error.message);
|
|
65
|
+
resolve(null);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
req.setTimeout(15000, () => {
|
|
70
|
+
req.destroy();
|
|
71
|
+
resolve(null);
|
|
72
|
+
});
|
|
73
|
+
req.on('error', (error) => {
|
|
74
|
+
console.error('[update] error consultando el registry:', error.message);
|
|
75
|
+
resolve(null);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async checkForUpdates() {
|
|
81
|
+
if (this.updating) return;
|
|
82
|
+
try {
|
|
83
|
+
// Mismos nombres que el updater viejo, incluida la errata 'lastest', para no
|
|
84
|
+
// romper los userData que ya están en la calle.
|
|
85
|
+
const desiredVersion = userData.get('desiredVersion') || 'lastest';
|
|
86
|
+
if (!desiredVersion) return;
|
|
87
|
+
|
|
88
|
+
const isLatest = desiredVersion === 'lastest' || desiredVersion === 'latest';
|
|
89
|
+
const target = isLatest ? 'latest' : desiredVersion;
|
|
90
|
+
const meta = await this.getJson(`${REGISTRY}/${npmMode.PKG_NAME}/${target}`);
|
|
91
|
+
if (!meta || !meta.version) return;
|
|
92
|
+
|
|
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
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('[update] error comprobando actualizaciones:', error.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
applyUpdate(version) {
|
|
106
|
+
if (this.updating) return;
|
|
107
|
+
this.updating = true;
|
|
108
|
+
this.stop();
|
|
109
|
+
console.log(`[update] ${this.currentVersion} → ${version}: actualizando y reiniciando`);
|
|
110
|
+
|
|
111
|
+
const cli = path.join(npmMode.PACKAGE_ROOT, 'bin', 'glassnote.js');
|
|
112
|
+
// electron sabe hacer de node con ELECTRON_RUN_AS_NODE: así el ayudante no
|
|
113
|
+
// depende de que haya un `node` en el PATH de la sesión gráfica.
|
|
114
|
+
const env = Object.assign({}, process.env, {
|
|
115
|
+
ELECTRON_RUN_AS_NODE: '1',
|
|
116
|
+
GLASSNOTE_INSTALL_MODE: 'npm-global',
|
|
117
|
+
PATH: `${npmMode.BIN_DIR}${path.delimiter}${process.env.PATH || ''}`,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const child = spawn(
|
|
122
|
+
process.execPath,
|
|
123
|
+
[cli, 'update', version, '--relaunch', '--wait', '4000'],
|
|
124
|
+
{ detached: true, stdio: 'ignore', env }
|
|
125
|
+
);
|
|
126
|
+
child.unref();
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('[update] no se pudo lanzar el actualizador:', error.message);
|
|
129
|
+
this.updating = false;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// La app se va: el ayudante espera unos segundos justamente para que ya no esté
|
|
134
|
+
// cuando npm reemplace los archivos.
|
|
135
|
+
const { app } = require('electron');
|
|
136
|
+
setTimeout(() => app.quit(), 500);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function compareVersions(a, b) {
|
|
141
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
142
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
143
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
144
|
+
const x = pa[i] || 0;
|
|
145
|
+
const y = pb[i] || 0;
|
|
146
|
+
if (x > y) return 1;
|
|
147
|
+
if (x < y) return -1;
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = NpmUpdate;
|
package/package.json
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glassnote/client",
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "GlassNote — cliente de escritorio de notas superpuestas. Se instala con npx, sin instaladores nativos ni firmas.",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"_notes": "Un paquete con scope se publica RESTRICTED por defecto: sin esto el publish falla, o peor, queda privado (plan pago).",
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"glassnote": "bin/glassnote.js"
|
|
12
|
+
},
|
|
13
|
+
"author": "GlassNote Team",
|
|
14
|
+
"license": "UNLICENSED",
|
|
15
|
+
"logRenderer": true,
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"os": [
|
|
20
|
+
"darwin",
|
|
21
|
+
"linux",
|
|
22
|
+
"win32"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
"bin/",
|
|
26
|
+
"images/",
|
|
27
|
+
"main.js",
|
|
28
|
+
"preload.js",
|
|
29
|
+
"autoStart.js",
|
|
30
|
+
"autoupdate.js",
|
|
31
|
+
"autoupdate_renderer.js",
|
|
32
|
+
"npmMode.js",
|
|
33
|
+
"npmUpdate.js",
|
|
34
|
+
"localserver.js",
|
|
35
|
+
"logUtilities.js",
|
|
36
|
+
"userData.js",
|
|
37
|
+
"INSTALACION.md",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"start": "node bin/glassnote.js",
|
|
42
|
+
"build": "electron-builder",
|
|
43
|
+
"build-mac": "electron-builder --mac",
|
|
44
|
+
"build-installer": "electron-builder --win --publish=never",
|
|
45
|
+
"dev": "concurrently \"npm run watch-renderer\" \"nodemon --watch . --ext html,js,ts,vue --exec electron .\"",
|
|
46
|
+
"watch": "concurrently \"npm run watch-renderer\" \"nodemon --watch . --ext html,js,ts,vue --exec electron .\"",
|
|
47
|
+
"build-renderer": "cd glassnote-renderer && npm run build",
|
|
48
|
+
"watch-renderer": "cd glassnote-renderer && npm run build:watch",
|
|
49
|
+
"install-local": "node bin/glassnote.js install",
|
|
50
|
+
"sync-installers": "node scripts/sync-installers.js",
|
|
51
|
+
"local-registry": "node scripts/local-registry.js",
|
|
52
|
+
"prepublishOnly": "node scripts/check-publish.js",
|
|
53
|
+
"start-electron": "electron ."
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"glassnote",
|
|
57
|
+
"overlay",
|
|
58
|
+
"notes",
|
|
59
|
+
"electron",
|
|
60
|
+
"desktop",
|
|
61
|
+
"npx",
|
|
62
|
+
"tray"
|
|
63
|
+
],
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+ssh://git@intermarkec/intermarkec/glassnote-electron.git"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@glassnote/renderer": "2.0.9",
|
|
70
|
+
"cors": "^2.8.5",
|
|
71
|
+
"electron": "^29.4.6",
|
|
72
|
+
"express": "^4.18.2",
|
|
73
|
+
"uuid": "^11.1.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"concurrently": "^8.2.2",
|
|
77
|
+
"electron-builder": "^26.0.12",
|
|
78
|
+
"electron-packager": "^17.1.2",
|
|
79
|
+
"nodemon": "^3.1.10",
|
|
80
|
+
"puppeteer": "^24.37.5"
|
|
81
|
+
},
|
|
82
|
+
"build": {
|
|
83
|
+
"appId": "com.glassnote.app",
|
|
84
|
+
"productName": "glassnote",
|
|
85
|
+
"directories": {
|
|
86
|
+
"output": "installer"
|
|
87
|
+
},
|
|
88
|
+
"files": [
|
|
89
|
+
"**/*",
|
|
90
|
+
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
|
|
91
|
+
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
|
|
92
|
+
"!**/node_modules/*.d.ts",
|
|
93
|
+
"!**/node_modules/.bin",
|
|
94
|
+
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
|
|
95
|
+
"!.editorconfig",
|
|
96
|
+
"!**/._*",
|
|
97
|
+
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
|
|
98
|
+
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
|
|
99
|
+
"!**/{appveyor.yml,.travis.yml,circle.yml}",
|
|
100
|
+
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
|
|
101
|
+
],
|
|
102
|
+
"mac": {
|
|
103
|
+
"category": "public.app-category.productivity",
|
|
104
|
+
"target": {
|
|
105
|
+
"target": "dmg",
|
|
106
|
+
"arch": "universal"
|
|
107
|
+
},
|
|
108
|
+
"icon": "images/icon-512.png",
|
|
109
|
+
"identity": "HHBGJVD63U",
|
|
110
|
+
"hardenedRuntime": true,
|
|
111
|
+
"gatekeeperAssess": true,
|
|
112
|
+
"entitlements": "entitlements.mac.plist",
|
|
113
|
+
"entitlementsInherit": "entitlements.mac.plist"
|
|
114
|
+
},
|
|
115
|
+
"dmg": {
|
|
116
|
+
"sign": true,
|
|
117
|
+
"contents": [
|
|
118
|
+
{
|
|
119
|
+
"x": 110,
|
|
120
|
+
"y": 150,
|
|
121
|
+
"type": "file"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"x": 340,
|
|
125
|
+
"y": 150,
|
|
126
|
+
"type": "link",
|
|
127
|
+
"path": "/Applications"
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"background": "images/dmg-background.png",
|
|
131
|
+
"window": {
|
|
132
|
+
"x": 400,
|
|
133
|
+
"y": 100,
|
|
134
|
+
"width": 800,
|
|
135
|
+
"height": 600
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"artifactName": "glassnote-installer-${version}.${ext}"
|
|
139
|
+
},
|
|
140
|
+
"build_notes": "DEPRECADO desde 2026-09-05: los instaladores nativos (NSIS/DMG, eSigner, notarizacion) ya no son la via oficial. La instalacion soportada es `npx @glassnote/client install`. Esta configuracion se conserva solo para builds heredados."
|
|
141
|
+
}
|
package/preload.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const { contextBridge, ipcRenderer } = require('electron');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
// Expose protected methods that allow the renderer process to use
|
|
5
|
+
// the ipcRenderer without exposing the entire object
|
|
6
|
+
contextBridge.exposeInMainWorld('electronAPI', {
|
|
7
|
+
// IPC communication methods
|
|
8
|
+
send: (channel, data) => {
|
|
9
|
+
// Whitelist channels
|
|
10
|
+
const validChannels = [
|
|
11
|
+
'set-ignore-events-true',
|
|
12
|
+
'set-ignore-events-false',
|
|
13
|
+
'get-user-data',
|
|
14
|
+
'set-user-data',
|
|
15
|
+
'remove-user-data',
|
|
16
|
+
'check-code-visible-response',
|
|
17
|
+
'show-window',
|
|
18
|
+
'hide-window',
|
|
19
|
+
'show-config-menu',
|
|
20
|
+
'open-external',
|
|
21
|
+
'open-external-browser',
|
|
22
|
+
'open-registration-window'
|
|
23
|
+
];
|
|
24
|
+
if (validChannels.includes(channel)) {
|
|
25
|
+
ipcRenderer.send(channel, data);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// External browser methods
|
|
30
|
+
openExternal: (url) => {
|
|
31
|
+
ipcRenderer.send('open-external', url);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
openExternalBrowser: (url) => {
|
|
35
|
+
ipcRenderer.send('open-external-browser', url);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
openRegistrationWindow: (url) => {
|
|
39
|
+
ipcRenderer.send('open-registration-window', url);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
receive: (channel, func) => {
|
|
43
|
+
// Whitelist channels
|
|
44
|
+
const validChannels = [
|
|
45
|
+
'app-data',
|
|
46
|
+
'user-data',
|
|
47
|
+
'servers-list',
|
|
48
|
+
'show-code',
|
|
49
|
+
'hide-code',
|
|
50
|
+
'check-code-visible',
|
|
51
|
+
'user-data-response',
|
|
52
|
+
'show-config-menu'
|
|
53
|
+
];
|
|
54
|
+
if (validChannels.includes(channel)) {
|
|
55
|
+
// Remove existing listener first to prevent duplicates
|
|
56
|
+
ipcRenderer.removeAllListeners(channel);
|
|
57
|
+
// Deliberately strip event as it includes `sender`
|
|
58
|
+
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// OS information (limited exposure)
|
|
63
|
+
getOSInfo: () => {
|
|
64
|
+
return {
|
|
65
|
+
platform: os.platform(),
|
|
66
|
+
networkInterfaces: () => {
|
|
67
|
+
const interfaces = os.networkInterfaces();
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const [name, iface] of Object.entries(interfaces)) {
|
|
70
|
+
result[name] = iface.filter(i => i.family === 'IPv4' && !i.internal)
|
|
71
|
+
.map(i => i.address);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Remove all listeners for a channel
|
|
79
|
+
removeAllListeners: (channel) => {
|
|
80
|
+
ipcRenderer.removeAllListeners(channel);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Window control convenience methods
|
|
84
|
+
showWindow: () => {
|
|
85
|
+
ipcRenderer.send('show-window');
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
hideWindow: () => {
|
|
89
|
+
ipcRenderer.send('hide-window');
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// UserData operations
|
|
93
|
+
getUserData: (key, nestedKey) => {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
ipcRenderer.once('user-data-response', (event, response) => {
|
|
96
|
+
|
|
97
|
+
if (response.success) {
|
|
98
|
+
// Handle IPC serialization issues - sometimes objects get lost
|
|
99
|
+
if (response.value === undefined || response.value === null) {
|
|
100
|
+
|
|
101
|
+
// Try to get the value directly from main process via another IPC call
|
|
102
|
+
ipcRenderer.send('get-user-data-direct', { key, nestedKey });
|
|
103
|
+
ipcRenderer.once('user-data-direct-response', (event, directResponse) => {
|
|
104
|
+
if (directResponse.success) {
|
|
105
|
+
// Parse the JSON string back to object
|
|
106
|
+
const parsedValue = directResponse.value ? JSON.parse(directResponse.value) : null;
|
|
107
|
+
resolve(parsedValue);
|
|
108
|
+
} else {
|
|
109
|
+
resolve(null);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
resolve(response.value);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
reject(new Error(response.error || 'Failed to get user data'));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
ipcRenderer.send('get-user-data', { key, nestedKey });
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
setUserData: (key, nestedKey, value) => {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
ipcRenderer.once('user-data-response', (event, response) => {
|
|
126
|
+
if (response.success) {
|
|
127
|
+
resolve(response.value);
|
|
128
|
+
} else {
|
|
129
|
+
reject(new Error(response.error || 'Failed to set user data'));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
ipcRenderer.send('set-user-data', { key, nestedKey, value });
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
removeUserData: (key, nestedKey) => {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
ipcRenderer.once('user-data-response', (event, response) => {
|
|
139
|
+
if (response.success) {
|
|
140
|
+
resolve(true);
|
|
141
|
+
} else {
|
|
142
|
+
reject(new Error(response.error || 'Failed to remove user data'));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
ipcRenderer.send('remove-user-data', { key, nestedKey });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|