@danieltmn/openbridge 0.6.0 → 0.6.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/CHANGELOG.md +22 -0
- package/README.md +12 -3
- package/package.json +1 -1
- package/src/bridge/bridge.js +26 -6
- package/src/cli.js +83 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ Todos los cambios relevantes de OpenBridge. Formato basado en
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y
|
|
5
5
|
[Versionado Semantico](https://semver.org/lang/es/).
|
|
6
6
|
|
|
7
|
+
## [0.6.2] - 2026-09-14
|
|
8
|
+
|
|
9
|
+
### Corregido
|
|
10
|
+
|
|
11
|
+
- **Tunel en Windows**: los shims `.cmd` (p. ej. `tmole`) se lanzaban con doble
|
|
12
|
+
cita (`cmd.exe /d /s /c "\"ruta\""`) y fallaban con "no se reconoce como un
|
|
13
|
+
comando". Ahora se usa `windowsVerbatimArguments` y, si `tmole` falla, el
|
|
14
|
+
puente **reintenta con `npx --yes tunnelmole`** (antes solo lo intentaba si
|
|
15
|
+
`tmole` no estaba en el PATH). Timeout del tunel: 85 s.
|
|
16
|
+
|
|
17
|
+
## [0.6.1] - 2026-09-14
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
|
|
21
|
+
- `openbridge bridge --background` (segundo plano; sobrevive al cierre de la
|
|
22
|
+
consola), `--stop` y `--status`. `openbridge pair`/`join` aceptan `--background`.
|
|
23
|
+
|
|
24
|
+
### Corregido
|
|
25
|
+
|
|
26
|
+
- `autostart` corre solo el puente cuando la PC pertenece a un hub remoto
|
|
27
|
+
(`join`/`pair`), en vez del server local.
|
|
28
|
+
|
|
7
29
|
## [0.6.0] - 2026-09-14
|
|
8
30
|
|
|
9
31
|
### Agregado
|
package/README.md
CHANGED
|
@@ -89,9 +89,9 @@ iniciar sesión** una vez; los chats, carpetas, túnel y push se conservan.
|
|
|
89
89
|
| `openbridge qr` | Muestra la URL (pública o local) como QR para escanear desde el celular |
|
|
90
90
|
| `openbridge tunnel` | Muestra o cambia el proveedor de túnel y su dominio fijo (`--domain`) |
|
|
91
91
|
| `openbridge logs` | Logs (`--follow`, `--server`, `--bridge`) |
|
|
92
|
-
| `openbridge bridge` | Corre **solo** el puente (`--
|
|
93
|
-
| `openbridge join` | Vincula esta PC como puente de un hub (`<url> --token --id --name`) |
|
|
94
|
-
| `openbridge pair` | Empareja esta PC con un **hub PHP** por código (`<url> [--id --name]`) |
|
|
92
|
+
| `openbridge bridge` | Corre **solo** el puente (`--background` = segundo plano, `--stop`, `--status`) |
|
|
93
|
+
| `openbridge join` | Vincula esta PC como puente de un hub (`<url> --token --id --name [--background]`) |
|
|
94
|
+
| `openbridge pair` | Empareja esta PC con un **hub PHP** por código (`<url> [--id --name] [--background]`) |
|
|
95
95
|
| `openbridge import` | Trae `data/` de OpenConex (`<data-dir> [--force]`) |
|
|
96
96
|
| `openbridge reset` | Borra chats/datos (`--session <id>`, `--yes`) |
|
|
97
97
|
| `openbridge autostart` | Arranque automático (`install`/`remove`) |
|
|
@@ -162,6 +162,15 @@ igual que OpenConex, y dejar que cada PC corra solo el puente:
|
|
|
162
162
|
|
|
163
163
|
Guía completa (requisitos, build, subida y emparejamiento): [`docs/DEPLOY-PHP.md`](docs/DEPLOY-PHP.md).
|
|
164
164
|
|
|
165
|
+
La PC corre el puente; para que siga tras cerrar la consola:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
openbridge bridge --background # arranca en segundo plano
|
|
169
|
+
openbridge bridge --status # ¿corre?
|
|
170
|
+
openbridge bridge --stop # detener
|
|
171
|
+
openbridge autostart install # arranca solo al iniciar sesión
|
|
172
|
+
```
|
|
173
|
+
|
|
165
174
|
## Túnel y URL estable
|
|
166
175
|
|
|
167
176
|
Proveedores (`--tunnel` o `openbridge tunnel <prov>`): `tunnelmole` (gratis, sin
|
package/package.json
CHANGED
package/src/bridge/bridge.js
CHANGED
|
@@ -1700,10 +1700,24 @@ function tunnelSpawn(port, onLine, onFail, onUrls, triedNpx = false) {
|
|
|
1700
1700
|
return;
|
|
1701
1701
|
}
|
|
1702
1702
|
const args = (spec.pre || []).concat(triedNpx ? ['--yes', 'tunnelmole', String(port)] : [String(port)]);
|
|
1703
|
+
let settled = false;
|
|
1704
|
+
// Si `tmole` existe pero falla (shim roto, sin PATH, etc.), reintentamos con
|
|
1705
|
+
// `npx --yes tunnelmole` en vez de darnos por vencidos.
|
|
1706
|
+
const retryNpx = () => {
|
|
1707
|
+
if (settled) return;
|
|
1708
|
+
settled = true;
|
|
1709
|
+
log('túnel: tmole falló, reintentando con npx tunnelmole…');
|
|
1710
|
+
tunnelSpawn(port, onLine, onFail, onUrls, true);
|
|
1711
|
+
};
|
|
1703
1712
|
let proc;
|
|
1704
1713
|
try {
|
|
1705
|
-
proc = spawn(spec.bin, args, {
|
|
1714
|
+
proc = spawn(spec.bin, args, {
|
|
1715
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1716
|
+
windowsHide: true,
|
|
1717
|
+
windowsVerbatimArguments: !!spec.verbatim,
|
|
1718
|
+
});
|
|
1706
1719
|
} catch (e) {
|
|
1720
|
+
if (!triedNpx) { retryNpx(); return; }
|
|
1707
1721
|
onFail('no se pudo iniciar tunnelmole (' + e.message + '). Instalalo con: npm i -g tunnelmole');
|
|
1708
1722
|
return;
|
|
1709
1723
|
}
|
|
@@ -1711,14 +1725,17 @@ function tunnelSpawn(port, onLine, onFail, onUrls, triedNpx = false) {
|
|
|
1711
1725
|
const feed = (d) => {
|
|
1712
1726
|
buf += String(d);
|
|
1713
1727
|
const urls = parseTunnelUrls(buf);
|
|
1714
|
-
if (urls.https || urls.http) onUrls(urls);
|
|
1728
|
+
if (urls.https || urls.http) { settled = true; onUrls(urls); }
|
|
1715
1729
|
};
|
|
1716
1730
|
proc.stdout.on('data', feed);
|
|
1717
1731
|
proc.stderr.on('data', feed);
|
|
1718
1732
|
proc.on('error', (e) => {
|
|
1733
|
+
if (!triedNpx) { retryNpx(); return; }
|
|
1719
1734
|
onFail('no se pudo iniciar tunnelmole (' + e.message + '). Instalalo con: npm i -g tunnelmole');
|
|
1720
1735
|
});
|
|
1721
1736
|
proc.on('exit', (code) => {
|
|
1737
|
+
if (settled) return;
|
|
1738
|
+
if (!triedNpx) { retryNpx(); return; }
|
|
1722
1739
|
onFail('tmole terminó (código ' + code + ')' + (buf ? ': ' + buf.slice(0, 160).trim() : ''));
|
|
1723
1740
|
});
|
|
1724
1741
|
onLine(proc);
|
|
@@ -1756,10 +1773,10 @@ async function tunnelStart(cmd) {
|
|
|
1756
1773
|
};
|
|
1757
1774
|
entry = { port, proc: null, urls: { https: '', http: '' }, startedAt: Date.now() };
|
|
1758
1775
|
tunnels.set(port, entry);
|
|
1759
|
-
//
|
|
1776
|
+
// 85 s: con el fallback de npx, la primera vez descarga tunnelmole.
|
|
1760
1777
|
timer = setTimeout(() => {
|
|
1761
|
-
finish(false, 'tunnelmole no devolvió URLs (
|
|
1762
|
-
},
|
|
1778
|
+
finish(false, 'tunnelmole no devolvió URLs (85 s). ¿Está instalado? npm i -g tunnelmole');
|
|
1779
|
+
}, 85000);
|
|
1763
1780
|
tunnelSpawn(
|
|
1764
1781
|
port,
|
|
1765
1782
|
(proc) => { entry.proc = proc; },
|
|
@@ -1913,7 +1930,9 @@ function procResolveBin(token, allowSet) {
|
|
|
1913
1930
|
const found = procFindOnPath(bare);
|
|
1914
1931
|
if (!found) throw new Error('no se encontró "' + token + '" en el PATH');
|
|
1915
1932
|
if (found.ext.toLowerCase() === '.exe') return { bin: found.p, pre: [] };
|
|
1916
|
-
|
|
1933
|
+
// Shim .cmd/.bat: se ejecuta con cmd.exe. `verbatim` evita que Node vuelva a
|
|
1934
|
+
// citar el argumento (que ya viene entre comillas) y cmd reciba "\"ruta\"".
|
|
1935
|
+
return { bin: 'cmd.exe', pre: ['/d', '/s', '/c', '"' + found.p + '"'], verbatim: true };
|
|
1917
1936
|
}
|
|
1918
1937
|
|
|
1919
1938
|
async function procDone(cmd, ok, payload, error) {
|
|
@@ -2000,6 +2019,7 @@ async function procStart(cmd) {
|
|
|
2000
2019
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2001
2020
|
env: process.env,
|
|
2002
2021
|
windowsHide: true,
|
|
2022
|
+
windowsVerbatimArguments: !!spec.verbatim,
|
|
2003
2023
|
});
|
|
2004
2024
|
} catch (e) {
|
|
2005
2025
|
await procDone(cmd, false, null, 'no se pudo iniciar: ' + e.message);
|
package/src/cli.js
CHANGED
|
@@ -682,12 +682,46 @@ async function cmdLogs(argv) {
|
|
|
682
682
|
// ---------------------------------------------------------------------------
|
|
683
683
|
// bridge (solo el puente, para modo hub remoto)
|
|
684
684
|
// ---------------------------------------------------------------------------
|
|
685
|
+
function bridgePid() {
|
|
686
|
+
try {
|
|
687
|
+
return parseInt(fs.readFileSync(paths.bridgeLockPath(), 'utf8'), 10) || 0;
|
|
688
|
+
} catch (e) {
|
|
689
|
+
return 0;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
685
693
|
async function cmdBridge(argv) {
|
|
686
694
|
const { flags } = parseArgs(argv);
|
|
687
695
|
if (!fs.existsSync(paths.configPath())) {
|
|
688
|
-
console.error('No hay configuracion de puente en ' + paths.home() + '. Corre primero: openbridge join <url> o openbridge
|
|
696
|
+
console.error('No hay configuracion de puente en ' + paths.home() + '. Corre primero: openbridge join <url> o openbridge pair <url>');
|
|
689
697
|
return 1;
|
|
690
698
|
}
|
|
699
|
+
|
|
700
|
+
if (flags.stop) {
|
|
701
|
+
const pid = bridgePid();
|
|
702
|
+
if (!pid || !pidAlive(pid)) {
|
|
703
|
+
try { fs.unlinkSync(paths.bridgeLockPath()); } catch (e) { /* nada */ }
|
|
704
|
+
console.log('El puente no esta corriendo.');
|
|
705
|
+
return 0;
|
|
706
|
+
}
|
|
707
|
+
killPid(pid);
|
|
708
|
+
try { fs.unlinkSync(paths.bridgeLockPath()); } catch (e) { /* nada */ }
|
|
709
|
+
console.log('Puente detenido (pid ' + pid + ').');
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const cfg = config.readBridge();
|
|
714
|
+
if (flags.status) {
|
|
715
|
+
const pid = bridgePid();
|
|
716
|
+
const live = pid && pidAlive(pid);
|
|
717
|
+
console.log('Puente: ' + (live ? 'corriendo (pid ' + pid + ')' : 'detenido'));
|
|
718
|
+
console.log(' hub : ' + (cfg.apiUrl || '(sin configurar)'));
|
|
719
|
+
console.log(' id : ' + (cfg.bridgeId || '(sin id)'));
|
|
720
|
+
console.log(' nombre : ' + (cfg.bridgeName || ''));
|
|
721
|
+
console.log(' log : ' + paths.bridgeLogPath());
|
|
722
|
+
return 0;
|
|
723
|
+
}
|
|
724
|
+
|
|
691
725
|
// Permite apuntar a otro hub sin editar config.json a mano.
|
|
692
726
|
const env = { ...process.env, OPENBRIDGE_HOME: paths.baseDir() };
|
|
693
727
|
if (flags.api) env.OPENBRIDGE_API_URL = String(flags.api);
|
|
@@ -695,6 +729,33 @@ async function cmdBridge(argv) {
|
|
|
695
729
|
if (flags.id) env.OPENBRIDGE_BRIDGE_ID = String(flags.id);
|
|
696
730
|
if (flags.name) env.OPENBRIDGE_BRIDGE_NAME = String(flags.name);
|
|
697
731
|
const bridgePath = path.join(__dirname, 'bridge', 'bridge.js');
|
|
732
|
+
|
|
733
|
+
// Segundo plano: detached + stdio ignorado; sobrevive al cierre de la consola.
|
|
734
|
+
if (flags.background || flags.bg || flags.detach || flags.d) {
|
|
735
|
+
const existing = bridgePid();
|
|
736
|
+
if (existing && pidAlive(existing)) {
|
|
737
|
+
console.log('El puente ya corre en segundo plano (pid ' + existing + ').');
|
|
738
|
+
console.log(' estado: openbridge bridge --status · detener: openbridge bridge --stop');
|
|
739
|
+
return 0;
|
|
740
|
+
}
|
|
741
|
+
const child = spawn(process.execPath, [bridgePath], {
|
|
742
|
+
cwd: paths.home(),
|
|
743
|
+
env,
|
|
744
|
+
stdio: 'ignore',
|
|
745
|
+
windowsHide: true,
|
|
746
|
+
detached: true,
|
|
747
|
+
});
|
|
748
|
+
child.unref();
|
|
749
|
+
await new Promise((r) => setTimeout(r, 900));
|
|
750
|
+
const pid = bridgePid() || child.pid;
|
|
751
|
+
console.log('Puente en segundo plano (pid ' + pid + ').');
|
|
752
|
+
console.log(' hub : ' + (cfg.apiUrl || '(sin configurar)'));
|
|
753
|
+
console.log(' log : ' + paths.bridgeLogPath());
|
|
754
|
+
console.log(' estado : openbridge bridge --status');
|
|
755
|
+
console.log(' detener: openbridge bridge --stop');
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
|
|
698
759
|
const child = spawn(process.execPath, [bridgePath], {
|
|
699
760
|
cwd: paths.home(),
|
|
700
761
|
env,
|
|
@@ -718,11 +779,16 @@ function hubApiUrl(raw) {
|
|
|
718
779
|
return u;
|
|
719
780
|
}
|
|
720
781
|
|
|
782
|
+
// Reenvia `--background` a `openbridge bridge` cuando join/pair lo piden.
|
|
783
|
+
function bridgeStartArgs(flags) {
|
|
784
|
+
return (flags.background || flags.bg || flags.detach || flags.d) ? ['--background'] : [];
|
|
785
|
+
}
|
|
786
|
+
|
|
721
787
|
async function cmdJoin(argv) {
|
|
722
788
|
const { flags, _ } = parseArgs(argv);
|
|
723
789
|
const url = String(_[0] || flags.api || '').trim();
|
|
724
790
|
if (!url || !/^https?:\/\//i.test(url)) {
|
|
725
|
-
console.error('Uso: openbridge join <url-del-hub> [--token <t>] [--id <pc>] [--name "<nombre>"] [--no-start]');
|
|
791
|
+
console.error('Uso: openbridge join <url-del-hub> [--token <t>] [--id <pc>] [--name "<nombre>"] [--no-start] [--background]');
|
|
726
792
|
console.error('Ej.: openbridge join https://mi-pc.trycloudflare.com --token <t> --id pc2 --name "PC oficina"');
|
|
727
793
|
console.error('(acepta la base, /chat.php o /api.php; se normaliza a /api.php)');
|
|
728
794
|
return 1;
|
|
@@ -747,7 +813,7 @@ async function cmdJoin(argv) {
|
|
|
747
813
|
}
|
|
748
814
|
console.log('');
|
|
749
815
|
console.log('Arrancando el puente (Ctrl+C para salir)...');
|
|
750
|
-
return cmdBridge(
|
|
816
|
+
return cmdBridge(bridgeStartArgs(flags));
|
|
751
817
|
}
|
|
752
818
|
|
|
753
819
|
// ---------------------------------------------------------------------------
|
|
@@ -771,7 +837,7 @@ async function cmdPair(argv) {
|
|
|
771
837
|
const { flags, _ } = parseArgs(argv);
|
|
772
838
|
const url = String(_[0] || flags.hub || flags.api || '').trim();
|
|
773
839
|
if (!/^https?:\/\//i.test(url)) {
|
|
774
|
-
console.error('Uso: openbridge pair <url-del-hub> [--id <pc>] [--name "<nombre>"] [--no-start]');
|
|
840
|
+
console.error('Uso: openbridge pair <url-del-hub> [--id <pc>] [--name "<nombre>"] [--no-start] [--background]');
|
|
775
841
|
console.error('Ej.: openbridge pair https://openbridge.tamnora.com --name "PC 1"');
|
|
776
842
|
return 1;
|
|
777
843
|
}
|
|
@@ -842,7 +908,7 @@ async function cmdPair(argv) {
|
|
|
842
908
|
return 0;
|
|
843
909
|
}
|
|
844
910
|
console.log('Arrancando el puente (Ctrl+C para salir)...');
|
|
845
|
-
return cmdBridge(
|
|
911
|
+
return cmdBridge(bridgeStartArgs(flags));
|
|
846
912
|
}
|
|
847
913
|
|
|
848
914
|
// ---------------------------------------------------------------------------
|
|
@@ -1062,12 +1128,18 @@ async function cmdAutostart(argv) {
|
|
|
1062
1128
|
}
|
|
1063
1129
|
try { fs.mkdirSync(path.dirname(target), { recursive: true }); } catch (e) { /* nada */ }
|
|
1064
1130
|
const home = paths.baseDir();
|
|
1131
|
+
// Si esta PC es un puente de un hub remoto (join/pair), el autostart corre
|
|
1132
|
+
// solo el puente; si es el hub local, corre el server.
|
|
1133
|
+
let acfg = { apiUrl: '' };
|
|
1134
|
+
try { acfg = config.readBridge(); } catch (e) { /* nada */ }
|
|
1135
|
+
const remote = /^https?:\/\//i.test(acfg.apiUrl || '') && !/127\.0\.0\.1|localhost/i.test(acfg.apiUrl);
|
|
1136
|
+
const runArgs = remote ? 'bridge' : 'server --stream --no-tunnel';
|
|
1065
1137
|
if (process.platform === 'win32') {
|
|
1066
1138
|
const ps = [
|
|
1067
1139
|
'$ws = New-Object -ComObject WScript.Shell;',
|
|
1068
1140
|
'$sc = $ws.CreateShortcut(' + JSON.stringify(target) + ');',
|
|
1069
1141
|
'$sc.TargetPath = ' + JSON.stringify(node) + ';',
|
|
1070
|
-
'$sc.Arguments = ' + JSON.stringify('"' + bin + '"
|
|
1142
|
+
'$sc.Arguments = ' + JSON.stringify('"' + bin + '" ' + runArgs) + ';',
|
|
1071
1143
|
'$sc.WorkingDirectory = ' + JSON.stringify(home) + ';',
|
|
1072
1144
|
'$sc.WindowStyle = 7;',
|
|
1073
1145
|
'$sc.Save();',
|
|
@@ -1080,7 +1152,8 @@ async function cmdAutostart(argv) {
|
|
|
1080
1152
|
+ '<plist version="1.0"><dict>'
|
|
1081
1153
|
+ '<key>Label</key><string>net.openbridge.server</string>'
|
|
1082
1154
|
+ '<key>ProgramArguments</key><array>'
|
|
1083
|
-
+ '<string>' + node + '</string><string>' + bin + '</string
|
|
1155
|
+
+ '<string>' + node + '</string><string>' + bin + '</string>'
|
|
1156
|
+
+ runArgs.split(' ').map((a) => '<string>' + a + '</string>').join('')
|
|
1084
1157
|
+ '</array>'
|
|
1085
1158
|
+ '<key>WorkingDirectory</key><string>' + home + '</string>'
|
|
1086
1159
|
+ '<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>'
|
|
@@ -1089,7 +1162,7 @@ async function cmdAutostart(argv) {
|
|
|
1089
1162
|
} else {
|
|
1090
1163
|
const unit = '[Unit]\nDescription=OpenBridge\nAfter=network-online.target\n\n'
|
|
1091
1164
|
+ '[Service]\nType=simple\n'
|
|
1092
|
-
+ 'ExecStart=' + node + ' ' + bin + '
|
|
1165
|
+
+ 'ExecStart=' + node + ' ' + bin + ' ' + runArgs + '\n'
|
|
1093
1166
|
+ 'WorkingDirectory=' + home + '\nRestart=on-failure\n\n'
|
|
1094
1167
|
+ '[Install]\nWantedBy=default.target\n';
|
|
1095
1168
|
fs.writeFileSync(target, unit);
|
|
@@ -1097,7 +1170,7 @@ async function cmdAutostart(argv) {
|
|
|
1097
1170
|
spawnSync('systemctl', ['--user', 'enable', '--now', 'openbridge.service'], { windowsHide: true });
|
|
1098
1171
|
}
|
|
1099
1172
|
console.log('autostart instalado: ' + target);
|
|
1100
|
-
console.log(' (arranca `openbridge
|
|
1173
|
+
console.log(' (arranca `openbridge ' + runArgs + '` en ' + home + ')');
|
|
1101
1174
|
return 0;
|
|
1102
1175
|
}
|
|
1103
1176
|
|
|
@@ -1202,7 +1275,7 @@ function usage() {
|
|
|
1202
1275
|
console.log(' qr Muestra la URL (publica o local) como QR para el celular');
|
|
1203
1276
|
console.log(' tunnel Muestra o cambia el proveedor de tunel (tunnelmole|ngrok|cloudflare|none) [--domain]');
|
|
1204
1277
|
console.log(' logs Ultimas lineas de los logs (--follow --server --bridge)');
|
|
1205
|
-
console.log(' bridge Corre solo el puente (--
|
|
1278
|
+
console.log(' bridge Corre solo el puente (--background | --stop | --status)');
|
|
1206
1279
|
console.log(' join Vincula esta PC como puente de un hub (<url> --token --id --name)');
|
|
1207
1280
|
console.log(' pair Empareja esta PC con un hub PHP por codigo (<url> [--id --name])');
|
|
1208
1281
|
console.log(' import Trae data/ de OpenConex (<data-dir> [--force])');
|