@danieltmn/openbridge 0.1.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/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/bin/openbridge.js +18 -0
- package/docs/ARCHITECTURE.md +77 -0
- package/package.json +54 -0
- package/src/auth.js +204 -0
- package/src/bridge/bridge.js +2280 -0
- package/src/cli.js +791 -0
- package/src/config.js +108 -0
- package/src/log.js +22 -0
- package/src/paths.js +152 -0
- package/src/push.js +85 -0
- package/src/store/index.js +930 -0
- package/src/store/jsonfile.js +54 -0
- package/src/tunnel/index.js +141 -0
- package/src/web/assets/app.js +3939 -0
- package/src/web/assets/icons/icon-128x128.png +0 -0
- package/src/web/assets/icons/icon-144x144.png +0 -0
- package/src/web/assets/icons/icon-152x152.png +0 -0
- package/src/web/assets/icons/icon-192x192.png +0 -0
- package/src/web/assets/icons/icon-384x384.png +0 -0
- package/src/web/assets/icons/icon-512x512.png +0 -0
- package/src/web/assets/icons/icon-72x72.png +0 -0
- package/src/web/assets/icons/icon-96x96.png +0 -0
- package/src/web/assets/manifest.webmanifest +24 -0
- package/src/web/assets/sw.js +85 -0
- package/src/web/assets/themes/dark-plus.css +19 -0
- package/src/web/assets/themes/dark-red.css +18 -0
- package/src/web/assets/themes/default.css +18 -0
- package/src/web/assets/themes/github-light.css +19 -0
- package/src/web/assets/themes/high-contrast.css +18 -0
- package/src/web/assets/themes/index.json +15 -0
- package/src/web/assets/themes/light-plus.css +19 -0
- package/src/web/assets/themes/light.css +18 -0
- package/src/web/assets/themes/monokai.css +19 -0
- package/src/web/assets/themes/one-dark.css +19 -0
- package/src/web/assets/themes/solarized.css +18 -0
- package/src/web/assets/themes/terminal.css +28 -0
- package/src/web/assets/themes/themes.css +2 -0
- package/src/web/routes.js +871 -0
- package/src/web/server.js +130 -0
- package/src/web/templates/chat.html +1296 -0
- package/src/web/templates/login.html +144 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<meta name="theme-color" content="#0f172a" id="themeColor">
|
|
7
|
+
<title>OpenBridge · acceso</title>
|
|
8
|
+
<link rel="manifest" href="manifest.webmanifest">
|
|
9
|
+
<link rel="icon" href="icons/icon-192x192.png">
|
|
10
|
+
<link rel="apple-touch-icon" href="icons/icon-192x192.png">
|
|
11
|
+
<link rel="apple-touch-icon" sizes="152x152" href="icons/icon-152x152.png">
|
|
12
|
+
<link rel="stylesheet" href="themes/themes.css" id="themeStylesheet">
|
|
13
|
+
<style>
|
|
14
|
+
* { box-sizing: border-box; }
|
|
15
|
+
body {
|
|
16
|
+
margin: 0; font-family: ui-monospace, "Cascadia Code", "JetBrains Mono", Consolas, "Roboto Mono", monospace;
|
|
17
|
+
background: var(--bg); color: var(--text); min-height: 100vh; min-height: 100dvh;
|
|
18
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
19
|
+
padding: 16px;
|
|
20
|
+
}
|
|
21
|
+
.card {
|
|
22
|
+
background: var(--panel); color: var(--text); border: 1px solid var(--border);
|
|
23
|
+
border-radius: 14px; padding: 26px 24px 20px; width: 100%;
|
|
24
|
+
max-width: 380px; box-shadow: var(--shadow);
|
|
25
|
+
}
|
|
26
|
+
.brand { display: flex; align-items: center; gap: 10px; }
|
|
27
|
+
.brand .logo { color: var(--mine); font-weight: 700; font-size: 17px; letter-spacing: -.05em; }
|
|
28
|
+
h1 { margin: 0; font-size: 18px; font-weight: 700; }
|
|
29
|
+
p.sub { margin: 2px 0 22px; color: var(--muted); font-size: 12.5px; }
|
|
30
|
+
.error {
|
|
31
|
+
background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--danger);
|
|
32
|
+
padding: 10px 12px; border-radius: 8px; font-size: 12.5px; margin-bottom: 16px;
|
|
33
|
+
animation: shake .35s ease;
|
|
34
|
+
}
|
|
35
|
+
@keyframes shake {
|
|
36
|
+
0%, 100% { transform: translateX(0); }
|
|
37
|
+
25% { transform: translateX(-5px); }
|
|
38
|
+
75% { transform: translateX(5px); }
|
|
39
|
+
}
|
|
40
|
+
.field {
|
|
41
|
+
display: flex; align-items: center; gap: 8px;
|
|
42
|
+
border: 1px solid var(--border); border-radius: 10px;
|
|
43
|
+
background: var(--bg); padding: 0 12px; margin-bottom: 12px;
|
|
44
|
+
transition: border-color .15s ease;
|
|
45
|
+
}
|
|
46
|
+
.field:focus-within { border-color: var(--mine); }
|
|
47
|
+
.field .prompt { color: var(--mine); font-weight: 700; user-select: none; flex-shrink: 0; }
|
|
48
|
+
.field input {
|
|
49
|
+
flex: 1; min-width: 0; border: 0; background: transparent; color: var(--text);
|
|
50
|
+
font: inherit; font-size: 15.5px; padding: 12px 0; outline: none;
|
|
51
|
+
}
|
|
52
|
+
.field input::placeholder { color: var(--muted); }
|
|
53
|
+
.peek {
|
|
54
|
+
background: transparent; border: 0; color: var(--muted); cursor: pointer;
|
|
55
|
+
font-size: 14px; padding: 4px 2px; flex-shrink: 0; line-height: 1;
|
|
56
|
+
}
|
|
57
|
+
.peek:hover, .peek.on { color: var(--accent); }
|
|
58
|
+
.keep {
|
|
59
|
+
display: flex; align-items: center; gap: 9px; cursor: pointer;
|
|
60
|
+
color: var(--muted); font-size: 12.5px; margin: 2px 0 18px; user-select: none;
|
|
61
|
+
position: relative;
|
|
62
|
+
}
|
|
63
|
+
.keep input { position: absolute; opacity: 0; width: 1px; height: 1px; }
|
|
64
|
+
.keep .box {
|
|
65
|
+
width: 16px; height: 16px; border: 1px solid var(--border); border-radius: 4px;
|
|
66
|
+
background: var(--bg); display: inline-flex; align-items: center; justify-content: center;
|
|
67
|
+
color: var(--mine); font-size: 12px; line-height: 1; flex-shrink: 0;
|
|
68
|
+
transition: border-color .12s ease;
|
|
69
|
+
}
|
|
70
|
+
.keep input:checked + .box::after { content: "✓"; }
|
|
71
|
+
.keep:hover .box { border-color: var(--mine); }
|
|
72
|
+
.keep input:focus-visible + .box { border-color: var(--accent); }
|
|
73
|
+
button[type="submit"] {
|
|
74
|
+
width: 100%; padding: 12px; border: 0; border-radius: 10px; background: var(--mine);
|
|
75
|
+
color: #fff; font: inherit; font-size: 15px; font-weight: 700; cursor: pointer;
|
|
76
|
+
letter-spacing: .02em;
|
|
77
|
+
}
|
|
78
|
+
button[type="submit"]:hover { filter: brightness(1.08); }
|
|
79
|
+
button[type="submit"]:active { transform: scale(.99); }
|
|
80
|
+
button[type="submit"]:disabled { opacity: .55; cursor: wait; }
|
|
81
|
+
.hint { text-align: center; color: var(--muted); opacity: .65; font-size: 10.5px; margin-top: 12px; }
|
|
82
|
+
@media (max-width: 479px) {
|
|
83
|
+
.card { padding: 22px 18px 16px; border-radius: 12px; }
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body data-theme="{{THEME}}">
|
|
88
|
+
<script>
|
|
89
|
+
(function () {
|
|
90
|
+
var knownThemes = {{THEMES_JSON}};
|
|
91
|
+
var params = new URLSearchParams(location.search);
|
|
92
|
+
var cookie = document.cookie.match(/(?:^|;\s*)ocx_theme=([^;]+)/);
|
|
93
|
+
var saved = (params.get('theme') || (cookie && decodeURIComponent(cookie[1])) || document.body.getAttribute('data-theme') || 'terminal');
|
|
94
|
+
if (knownThemes.indexOf(saved) === -1) saved = 'terminal';
|
|
95
|
+
document.body.setAttribute('data-theme', saved);
|
|
96
|
+
document.getElementById('themeStylesheet').setAttribute('href', 'themes/' + saved + '.css');
|
|
97
|
+
document.cookie = 'ocx_theme=' + encodeURIComponent(saved) + ';path=/;max-age=' + (60 * 60 * 24 * 365) + ';SameSite=Lax';
|
|
98
|
+
window.addEventListener('load', function () {
|
|
99
|
+
var bg = getComputedStyle(document.documentElement).getPropertyValue('--bg').trim();
|
|
100
|
+
if (bg) document.getElementById('themeColor').setAttribute('content', bg);
|
|
101
|
+
});
|
|
102
|
+
})();
|
|
103
|
+
</script>
|
|
104
|
+
<form class="card" method="post" autocomplete="on">
|
|
105
|
+
<div class="brand">
|
|
106
|
+
<span class="logo">❯_</span>
|
|
107
|
+
<h1>OpenBridge</h1>
|
|
108
|
+
</div>
|
|
109
|
+
<p class="sub">Tu opencode en el celular</p>
|
|
110
|
+
{{ERROR_BLOCK}}
|
|
111
|
+
<input type="hidden" name="csrf" value="{{CSRF}}">
|
|
112
|
+
<label class="field">
|
|
113
|
+
<span class="prompt">❯</span>
|
|
114
|
+
<input type="password" id="password" name="password" placeholder="contraseña" required autofocus autocomplete="current-password">
|
|
115
|
+
<button type="button" class="peek" id="btnPeek" aria-label="Mostrar u ocultar contraseña" tabindex="-1">👁</button>
|
|
116
|
+
</label>
|
|
117
|
+
<label class="keep" for="keep">
|
|
118
|
+
<input type="checkbox" id="keep" name="keep" value="1" checked>
|
|
119
|
+
<span class="box"></span>
|
|
120
|
+
mantener la sesión abierta
|
|
121
|
+
</label>
|
|
122
|
+
<button type="submit" id="btnEnter">Entrar</button>
|
|
123
|
+
<div class="hint">openbridge · puente celular ↔ opencode</div>
|
|
124
|
+
</form>
|
|
125
|
+
<script>
|
|
126
|
+
(function () {
|
|
127
|
+
var pw = document.getElementById('password');
|
|
128
|
+
var peek = document.getElementById('btnPeek');
|
|
129
|
+
peek.addEventListener('click', function () {
|
|
130
|
+
var show = pw.type === 'password';
|
|
131
|
+
pw.type = show ? 'text' : 'password';
|
|
132
|
+
peek.classList.toggle('on', show);
|
|
133
|
+
pw.focus();
|
|
134
|
+
});
|
|
135
|
+
var form = document.querySelector('form.card');
|
|
136
|
+
form.addEventListener('submit', function () {
|
|
137
|
+
var b = document.getElementById('btnEnter');
|
|
138
|
+
b.disabled = true;
|
|
139
|
+
b.textContent = 'entrando…';
|
|
140
|
+
});
|
|
141
|
+
})();
|
|
142
|
+
</script>
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|