opencodespaces 1.1.6 → 1.2.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/dist/commands/sync.js +33 -2
- package/dist/index.js +1 -0
- package/dist/lib/auth.js +528 -42
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -17,7 +17,7 @@ import os from 'os';
|
|
|
17
17
|
import ora from 'ora';
|
|
18
18
|
import inquirer from 'inquirer';
|
|
19
19
|
import { isLoggedIn } from '../lib/auth.js';
|
|
20
|
-
import { api } from '../lib/api.js';
|
|
20
|
+
import { api, ApiError } from '../lib/api.js';
|
|
21
21
|
import { getIgnoreList, saveSessionKey, deleteSessionKey, } from '../lib/config.js';
|
|
22
22
|
import { logger } from '../utils/logger.js';
|
|
23
23
|
// SSH config directory
|
|
@@ -116,7 +116,38 @@ async function startSync(sessionId, localDir) {
|
|
|
116
116
|
const spinner = ora('Initializing sync...').start();
|
|
117
117
|
try {
|
|
118
118
|
// Initialize sync on server (get SSH credentials)
|
|
119
|
-
|
|
119
|
+
let syncInfo;
|
|
120
|
+
try {
|
|
121
|
+
syncInfo = await api.initSync(sessionId);
|
|
122
|
+
}
|
|
123
|
+
catch (initError) {
|
|
124
|
+
// Handle "Sync already active" error (409)
|
|
125
|
+
if (initError instanceof ApiError && initError.statusCode === 409) {
|
|
126
|
+
spinner.stop();
|
|
127
|
+
logger.warn('A previous sync connection exists on the server.');
|
|
128
|
+
const { cleanup } = await inquirer.prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'confirm',
|
|
131
|
+
name: 'cleanup',
|
|
132
|
+
message: 'Clean up stale connection and retry?',
|
|
133
|
+
default: true,
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
if (cleanup) {
|
|
137
|
+
spinner.start('Cleaning up stale connection...');
|
|
138
|
+
await api.stopSync(sessionId);
|
|
139
|
+
spinner.text = 'Initializing sync...';
|
|
140
|
+
syncInfo = await api.initSync(sessionId);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
logger.info('Run "opencodespaces sync stop" to manually clean up.');
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw initError;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
120
151
|
// Save private key
|
|
121
152
|
const keyPath = saveSessionKey(sessionId, syncInfo.privateKey);
|
|
122
153
|
spinner.text = 'Configuring SSH...';
|
package/dist/index.js
CHANGED
package/dist/lib/auth.js
CHANGED
|
@@ -15,6 +15,522 @@ import { saveCredentials, loadCredentials, deleteCredentials, getServerUrl, } fr
|
|
|
15
15
|
// Port range to try for local OAuth callback server
|
|
16
16
|
const PORT_RANGE_START = 9876;
|
|
17
17
|
const PORT_RANGE_END = 9900;
|
|
18
|
+
/**
|
|
19
|
+
* Generate the terminal-style success page HTML
|
|
20
|
+
*/
|
|
21
|
+
function generateSuccessPage(name, email, initials, avatarUrl) {
|
|
22
|
+
return `<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>OpenCodeSpaces - CLI Login</title>
|
|
28
|
+
<style>
|
|
29
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
30
|
+
|
|
31
|
+
body {
|
|
32
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
background: linear-gradient(180deg, hsl(258, 77%, 12%), hsl(258, 77%, 5%));
|
|
39
|
+
color: #f1f5f9;
|
|
40
|
+
padding: 1rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.logo {
|
|
44
|
+
margin-bottom: 2rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.logo img {
|
|
48
|
+
height: 43px;
|
|
49
|
+
width: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.terminal {
|
|
53
|
+
width: 100%;
|
|
54
|
+
max-width: 32rem;
|
|
55
|
+
background: rgba(12, 10, 29, 0.9);
|
|
56
|
+
backdrop-filter: blur(12px);
|
|
57
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
58
|
+
border-radius: 0.5rem;
|
|
59
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.terminal-header {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
padding: 0.75rem 1rem;
|
|
68
|
+
border-bottom: 1px solid rgba(139, 92, 246, 0.3);
|
|
69
|
+
background: rgba(88, 28, 135, 0.3);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dots {
|
|
73
|
+
display: flex;
|
|
74
|
+
gap: 0.375rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dot {
|
|
78
|
+
width: 0.75rem;
|
|
79
|
+
height: 0.75rem;
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.dot-red { background: rgba(239, 68, 68, 0.8); }
|
|
84
|
+
.dot-yellow { background: rgba(234, 179, 8, 0.8); }
|
|
85
|
+
.dot-green { background: rgba(34, 197, 94, 0.8); }
|
|
86
|
+
|
|
87
|
+
.terminal-title {
|
|
88
|
+
margin-left: 0.5rem;
|
|
89
|
+
font-size: 0.75rem;
|
|
90
|
+
color: #6b7280;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.terminal-content {
|
|
94
|
+
padding: 1rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.line {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 0.5rem;
|
|
101
|
+
font-size: 0.875rem;
|
|
102
|
+
margin-bottom: 0.75rem;
|
|
103
|
+
opacity: 0;
|
|
104
|
+
transform: translateX(-8px);
|
|
105
|
+
animation: slideIn 0.3s ease forwards;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.line:nth-child(1) { animation-delay: 0.1s; }
|
|
109
|
+
.line:nth-child(2) { animation-delay: 0.4s; }
|
|
110
|
+
.line:nth-child(3) { animation-delay: 0.7s; }
|
|
111
|
+
.line:nth-child(4) { animation-delay: 1.0s; }
|
|
112
|
+
.line:nth-child(5) { animation-delay: 1.3s; }
|
|
113
|
+
.line:nth-child(6) { animation-delay: 1.6s; }
|
|
114
|
+
.line:nth-child(7) { animation-delay: 1.9s; }
|
|
115
|
+
|
|
116
|
+
@keyframes slideIn {
|
|
117
|
+
to {
|
|
118
|
+
opacity: 1;
|
|
119
|
+
transform: translateX(0);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.prompt {
|
|
124
|
+
color: #a78bfa;
|
|
125
|
+
font-weight: 600;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.text {
|
|
129
|
+
color: #f1f5f9;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.check {
|
|
133
|
+
color: #22c55e;
|
|
134
|
+
margin-left: auto;
|
|
135
|
+
animation: zoomIn 0.2s ease forwards;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@keyframes zoomIn {
|
|
139
|
+
from {
|
|
140
|
+
transform: scale(0);
|
|
141
|
+
opacity: 0;
|
|
142
|
+
}
|
|
143
|
+
to {
|
|
144
|
+
transform: scale(1);
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.user-info {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 0.75rem;
|
|
153
|
+
padding: 0.75rem 0;
|
|
154
|
+
margin-bottom: 0.75rem;
|
|
155
|
+
opacity: 0;
|
|
156
|
+
transform: translateY(8px);
|
|
157
|
+
animation: fadeUp 0.5s ease forwards;
|
|
158
|
+
animation-delay: 1.3s;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@keyframes fadeUp {
|
|
162
|
+
to {
|
|
163
|
+
opacity: 1;
|
|
164
|
+
transform: translateY(0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.avatar {
|
|
169
|
+
width: 2.5rem;
|
|
170
|
+
height: 2.5rem;
|
|
171
|
+
border-radius: 50%;
|
|
172
|
+
background: linear-gradient(135deg, #a78bfa, #14b8a6);
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
justify-content: center;
|
|
176
|
+
font-size: 0.875rem;
|
|
177
|
+
font-weight: 600;
|
|
178
|
+
overflow: hidden;
|
|
179
|
+
border: 2px solid rgba(139, 92, 246, 0.5);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.avatar img {
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: 100%;
|
|
185
|
+
object-fit: cover;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.user-details {
|
|
189
|
+
display: flex;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.user-name {
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
color: #f1f5f9;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.user-email {
|
|
199
|
+
font-size: 0.875rem;
|
|
200
|
+
color: #6b7280;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.progress-section {
|
|
204
|
+
margin-top: 1rem;
|
|
205
|
+
padding-top: 1rem;
|
|
206
|
+
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
|
207
|
+
opacity: 0;
|
|
208
|
+
animation: fadeIn 0.5s ease forwards;
|
|
209
|
+
animation-delay: 2.2s;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@keyframes fadeIn {
|
|
213
|
+
to { opacity: 1; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.progress-bar {
|
|
217
|
+
height: 0.375rem;
|
|
218
|
+
background: rgba(88, 28, 135, 0.5);
|
|
219
|
+
border-radius: 9999px;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
margin-bottom: 0.5rem;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.progress-fill {
|
|
225
|
+
height: 100%;
|
|
226
|
+
background: linear-gradient(to right, #a78bfa, #14b8a6);
|
|
227
|
+
border-radius: 9999px;
|
|
228
|
+
width: 0%;
|
|
229
|
+
animation: fillProgress 5s linear forwards;
|
|
230
|
+
animation-delay: 2.5s;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@keyframes fillProgress {
|
|
234
|
+
to { width: 100%; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.countdown {
|
|
238
|
+
text-align: center;
|
|
239
|
+
font-size: 0.75rem;
|
|
240
|
+
color: #6b7280;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.hint {
|
|
244
|
+
margin-top: 1.5rem;
|
|
245
|
+
text-align: center;
|
|
246
|
+
font-size: 0.75rem;
|
|
247
|
+
color: #4b5563;
|
|
248
|
+
max-width: 28rem;
|
|
249
|
+
line-height: 1.5;
|
|
250
|
+
}
|
|
251
|
+
</style>
|
|
252
|
+
</head>
|
|
253
|
+
<body>
|
|
254
|
+
<div class="logo">
|
|
255
|
+
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEyOTggMjAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zOnNlcmlmPSJodHRwOi8vd3d3LnNlcmlmLmNvbS8iIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MjsiPgogICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwtMC41ODA3NjEsLTAuMDA1NDU1KSI+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTguMDEyLDg3Mi4zMDRMMjYwLjI5Miw4NzIuNjI5QzMxMS44NDcsODcyLjc1OCAzNTUuMzQzLDg2Ni4zNjcgNDAwLjc0LDg5OC4wMzJDNDc5LjEzNSw5NTIuNzEyIDQ4OC4wNzEsMTA2My4zNyA0MjEuMzYxLDExMzIuMDdMNDIwLjUzNywxMTMyLjlDNDAyLjE5MSwxMTUxLjE5IDM3OS4xNzUsMTE2NC4wOSAzNTMuOTk5LDExNzAuMkMzMjguODUzLDExNzYuNDIgMjkxLjI5MiwxMTc0LjY0IDI2NC41MTUsMTE3NC42NkwxNTguMDI5LDExNzQuNzlMMTU3Ljc1Myw5NjMuOTI5TDE1Ny4zMjIsOTA2LjAyNUMxNTcuMjQ3LDg5Ny43NDYgMTU2Ljc0Niw4NzkuODY2IDE1OC4wMTIsODcyLjMwNFoiIHN0eWxlPSJmaWxsOnJnYig5OCwxMTEsMjQwKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMTYuMzYxLDk1Mi4yNjNMMzg3Ljg0NSw5NTIuNDg5TDM4Ny45ODEsOTg5LjI3NUwyMTYuNDc0LDk4OS4yMjJMMjE2LjM2MSw5NTIuMjYzWiIgc3R5bGU9ImZpbGw6cmdiKDMxLDE0LDcxKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yOTYuNjEsMTA2MS4zN0MzMTEuMzA3LDEwNTguMjUgMzI1Ljc3NiwxMDY3LjU1IDMyOS4wMzQsMTA4Mi4yMkMzMzIuMjkzLDEwOTYuODggMzIzLjEyNCwxMTExLjQ0IDMwOC40ODcsMTExNC44M0MyOTMuNjU5LDExMTguMjcgMjc4Ljg3NSwxMTA4Ljk1IDI3NS41NzMsMTA5NC4wOUMyNzIuMjcyLDEwNzkuMjQgMjgxLjcyMiwxMDY0LjU0IDI5Ni42MSwxMDYxLjM3WiIgc3R5bGU9ImZpbGw6cmdiKDMxLDE0LDcxKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoOS4yNTk4NTUsMCwwLDkuMjU5ODU1LC0yMjgzLjQxNTc3OCwtMzQwNi4xMzM4MjEpIj4KICAgICAgICAgICAgPHRleHQgeD0iMjc1LjA5cHgiIHk9IjM4MS45NThweCIgc3R5bGU9ImZvbnQtZmFtaWx5OidNb250c2VycmF0LUJvbGQnLCAnTW9udHNlcnJhdCc7Zm9udC13ZWlnaHQ6NzAwO2ZvbnQtc2l6ZToxMnB4O2ZpbGw6d2hpdGU7Ij5PcGVuQzx0c3BhbiB4PSIzMTcuOTNweCAiIHk9IjM4MS45NThweCAiPm88L3RzcGFuPmRlPC90ZXh0PgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxMiwwLDAsMTIsMzg3LjE1ODM2NywzODEuOTU4MjkyKSI+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPHRleHQgeD0iMzQ0LjgxcHgiIHk9IjM4MS45NThweCIgc3R5bGU9ImZvbnQtZmFtaWx5OidNb250c2VycmF0LVJlZ3VsYXInLCAnTW9udHNlcnJhdCc7Zm9udC1zaXplOjEycHg7ZmlsbDp3aGl0ZTsiPlNwYWM8dHNwYW4geD0iMzc0LjA0MnB4ICIgeT0iMzgxLjk1OHB4ICI+ZTwvdHNwYW4+czwvdGV4dD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo=" alt="OpenCode Spaces" />
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div class="terminal">
|
|
259
|
+
<div class="terminal-header">
|
|
260
|
+
<div class="dots">
|
|
261
|
+
<div class="dot dot-red"></div>
|
|
262
|
+
<div class="dot dot-yellow"></div>
|
|
263
|
+
<div class="dot dot-green"></div>
|
|
264
|
+
</div>
|
|
265
|
+
<span class="terminal-title">opencodespaces — cli auth</span>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<div class="terminal-content">
|
|
269
|
+
<div class="line">
|
|
270
|
+
<span class="prompt">$</span>
|
|
271
|
+
<span class="text">opencodespaces login</span>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="line">
|
|
274
|
+
<span class="prompt">></span>
|
|
275
|
+
<span class="text">Opening browser for authentication...</span>
|
|
276
|
+
<span class="check">✓</span>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="line">
|
|
279
|
+
<span class="prompt">></span>
|
|
280
|
+
<span class="text">Google OAuth completed</span>
|
|
281
|
+
<span class="check">✓</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="line">
|
|
284
|
+
<span class="prompt">></span>
|
|
285
|
+
<span class="text">Token received from server</span>
|
|
286
|
+
<span class="check">✓</span>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="user-info">
|
|
290
|
+
<div class="avatar">${avatarUrl ? `<img src="${avatarUrl}" alt="${name}" />` : initials}</div>
|
|
291
|
+
<div class="user-details">
|
|
292
|
+
<span class="user-name">Welcome, ${name}!</span>
|
|
293
|
+
<span class="user-email">${email}</span>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div class="line" style="animation-delay: 1.6s;">
|
|
298
|
+
<span class="prompt">></span>
|
|
299
|
+
<span class="text">Authentication successful!</span>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="line" style="animation-delay: 1.9s;">
|
|
302
|
+
<span class="prompt">></span>
|
|
303
|
+
<span class="text">Credentials saved to ~/.opencodespaces/credentials</span>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="progress-section">
|
|
307
|
+
<div class="progress-bar">
|
|
308
|
+
<div class="progress-fill"></div>
|
|
309
|
+
</div>
|
|
310
|
+
<p class="countdown" id="countdown">This window will close in 5s</p>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<p class="hint">
|
|
316
|
+
You can close this window and return to your terminal.<br>
|
|
317
|
+
The CLI has received your credentials.
|
|
318
|
+
</p>
|
|
319
|
+
|
|
320
|
+
<script>
|
|
321
|
+
let seconds = 5;
|
|
322
|
+
const countdownEl = document.getElementById('countdown');
|
|
323
|
+
|
|
324
|
+
const interval = setInterval(() => {
|
|
325
|
+
seconds--;
|
|
326
|
+
if (seconds <= 0) {
|
|
327
|
+
clearInterval(interval);
|
|
328
|
+
countdownEl.textContent = '✓ Complete — you can close this tab';
|
|
329
|
+
} else {
|
|
330
|
+
countdownEl.textContent = 'Completing in ' + seconds + 's...';
|
|
331
|
+
}
|
|
332
|
+
}, 1000);
|
|
333
|
+
</script>
|
|
334
|
+
</body>
|
|
335
|
+
</html>`;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Generate the terminal-style error page HTML
|
|
339
|
+
*/
|
|
340
|
+
function generateErrorPage(errorMessage) {
|
|
341
|
+
const errorMessages = {
|
|
342
|
+
'oauth_failed': 'Google sign-in failed',
|
|
343
|
+
'token_exchange_failed': 'Authentication failed',
|
|
344
|
+
'userinfo_failed': 'Could not get user info',
|
|
345
|
+
'no_code': 'Invalid OAuth response',
|
|
346
|
+
'oauth_not_configured': 'OAuth not configured on server',
|
|
347
|
+
'domain_not_allowed': 'Email domain not allowed',
|
|
348
|
+
};
|
|
349
|
+
const displayError = errorMessages[errorMessage] || errorMessage;
|
|
350
|
+
return `<!DOCTYPE html>
|
|
351
|
+
<html lang="en">
|
|
352
|
+
<head>
|
|
353
|
+
<meta charset="UTF-8">
|
|
354
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
355
|
+
<title>OpenCodeSpaces - CLI Login Failed</title>
|
|
356
|
+
<style>
|
|
357
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
358
|
+
|
|
359
|
+
body {
|
|
360
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
361
|
+
min-height: 100vh;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-direction: column;
|
|
364
|
+
align-items: center;
|
|
365
|
+
justify-content: center;
|
|
366
|
+
background: linear-gradient(180deg, hsl(258, 77%, 12%), hsl(258, 77%, 5%));
|
|
367
|
+
color: #f1f5f9;
|
|
368
|
+
padding: 1rem;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.logo {
|
|
372
|
+
margin-bottom: 2rem;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.logo img {
|
|
376
|
+
height: 43px;
|
|
377
|
+
width: auto;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.terminal {
|
|
381
|
+
width: 100%;
|
|
382
|
+
max-width: 32rem;
|
|
383
|
+
background: rgba(12, 10, 29, 0.9);
|
|
384
|
+
backdrop-filter: blur(12px);
|
|
385
|
+
border: 1px solid rgba(139, 92, 246, 0.3);
|
|
386
|
+
border-radius: 0.5rem;
|
|
387
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.terminal-header {
|
|
392
|
+
display: flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: 0.5rem;
|
|
395
|
+
padding: 0.75rem 1rem;
|
|
396
|
+
border-bottom: 1px solid rgba(139, 92, 246, 0.3);
|
|
397
|
+
background: rgba(88, 28, 135, 0.3);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.dots {
|
|
401
|
+
display: flex;
|
|
402
|
+
gap: 0.375rem;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.dot {
|
|
406
|
+
width: 0.75rem;
|
|
407
|
+
height: 0.75rem;
|
|
408
|
+
border-radius: 50%;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.dot-red { background: rgba(239, 68, 68, 0.8); }
|
|
412
|
+
.dot-yellow { background: rgba(234, 179, 8, 0.8); }
|
|
413
|
+
.dot-green { background: rgba(34, 197, 94, 0.8); }
|
|
414
|
+
|
|
415
|
+
.terminal-title {
|
|
416
|
+
margin-left: 0.5rem;
|
|
417
|
+
font-size: 0.75rem;
|
|
418
|
+
color: #6b7280;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.terminal-content {
|
|
422
|
+
padding: 1rem;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.line {
|
|
426
|
+
display: flex;
|
|
427
|
+
align-items: center;
|
|
428
|
+
gap: 0.5rem;
|
|
429
|
+
font-size: 0.875rem;
|
|
430
|
+
margin-bottom: 0.75rem;
|
|
431
|
+
opacity: 0;
|
|
432
|
+
transform: translateX(-8px);
|
|
433
|
+
animation: slideIn 0.3s ease forwards;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.line:nth-child(1) { animation-delay: 0.1s; }
|
|
437
|
+
.line:nth-child(2) { animation-delay: 0.4s; }
|
|
438
|
+
.line:nth-child(3) { animation-delay: 0.7s; }
|
|
439
|
+
|
|
440
|
+
@keyframes slideIn {
|
|
441
|
+
to {
|
|
442
|
+
opacity: 1;
|
|
443
|
+
transform: translateX(0);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.prompt { color: #a78bfa; font-weight: 600; }
|
|
448
|
+
.text { color: #f1f5f9; }
|
|
449
|
+
.check { color: #22c55e; margin-left: auto; }
|
|
450
|
+
.x { color: #ef4444; margin-left: auto; }
|
|
451
|
+
|
|
452
|
+
.error-section {
|
|
453
|
+
margin-top: 1rem;
|
|
454
|
+
padding: 1rem;
|
|
455
|
+
background: rgba(239, 68, 68, 0.1);
|
|
456
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
457
|
+
border-radius: 0.375rem;
|
|
458
|
+
opacity: 0;
|
|
459
|
+
animation: fadeIn 0.3s ease forwards;
|
|
460
|
+
animation-delay: 1s;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@keyframes fadeIn {
|
|
464
|
+
to { opacity: 1; }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.error-title {
|
|
468
|
+
display: flex;
|
|
469
|
+
align-items: center;
|
|
470
|
+
gap: 0.5rem;
|
|
471
|
+
color: #f87171;
|
|
472
|
+
font-weight: 600;
|
|
473
|
+
margin-bottom: 0.5rem;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.error-message {
|
|
477
|
+
color: #9ca3af;
|
|
478
|
+
font-size: 0.875rem;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.hint {
|
|
482
|
+
margin-top: 1.5rem;
|
|
483
|
+
text-align: center;
|
|
484
|
+
font-size: 0.75rem;
|
|
485
|
+
color: #4b5563;
|
|
486
|
+
}
|
|
487
|
+
</style>
|
|
488
|
+
</head>
|
|
489
|
+
<body>
|
|
490
|
+
<div class="logo">
|
|
491
|
+
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEyOTggMjAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zOnNlcmlmPSJodHRwOi8vd3d3LnNlcmlmLmNvbS8iIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MjsiPgogICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMSwwLDAsMSwtMC41ODA3NjEsLTAuMDA1NDU1KSI+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNTguMDEyLDg3Mi4zMDRMMjYwLjI5Miw4NzIuNjI5QzMxMS44NDcsODcyLjc1OCAzNTUuMzQzLDg2Ni4zNjcgNDAwLjc0LDg5OC4wMzJDNDc5LjEzNSw5NTIuNzEyIDQ4OC4wNzEsMTA2My4zNyA0MjEuMzYxLDExMzIuMDdMNDIwLjUzNywxMTMyLjlDNDAyLjE5MSwxMTUxLjE5IDM3OS4xNzUsMTE2NC4wOSAzNTMuOTk5LDExNzAuMkMzMjguODUzLDExNzYuNDIgMjkxLjI5MiwxMTc0LjY0IDI2NC41MTUsMTE3NC42NkwxNTguMDI5LDExNzQuNzlMMTU3Ljc1Myw5NjMuOTI5TDE1Ny4zMjIsOTA2LjAyNUMxNTcuMjQ3LDg5Ny43NDYgMTU2Ljc0Niw4NzkuODY2IDE1OC4wMTIsODcyLjMwNFoiIHN0eWxlPSJmaWxsOnJnYig5OCwxMTEsMjQwKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMTYuMzYxLDk1Mi4yNjNMMzg3Ljg0NSw5NTIuNDg5TDM4Ny45ODEsOTg5LjI3NUwyMTYuNDc0LDk4OS4yMjJMMjE2LjM2MSw5NTIuMjYzWiIgc3R5bGU9ImZpbGw6cmdiKDMxLDE0LDcxKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC42NTg4NTcsMCwwLDAuNjU4ODU3LC0xMDIuOTY2MTQ2LC01NzQuNjM3NjA4KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yOTYuNjEsMTA2MS4zN0MzMTEuMzA3LDEwNTguMjUgMzI1Ljc3NiwxMDY3LjU1IDMyOS4wMzQsMTA4Mi4yMkMzMzIuMjkzLDEwOTYuODggMzIzLjEyNCwxMTExLjQ0IDMwOC40ODcsMTExNC44M0MyOTMuNjU5LDExMTguMjcgMjc4Ljg3NSwxMTA4Ljk1IDI3NS41NzMsMTA5NC4wOUMyNzIuMjcyLDEwNzkuMjQgMjgxLjcyMiwxMDY0LjU0IDI5Ni42MSwxMDYxLjM3WiIgc3R5bGU9ImZpbGw6cmdiKDMxLDE0LDcxKTtmaWxsLXJ1bGU6bm9uemVybzsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoOS4yNTk4NTUsMCwwLDkuMjU5ODU1LC0yMjgzLjQxNTc3OCwtMzQwNi4xMzM4MjEpIj4KICAgICAgICAgICAgPHRleHQgeD0iMjc1LjA5cHgiIHk9IjM4MS45NThweCIgc3R5bGU9ImZvbnQtZmFtaWx5OidNb250c2VycmF0LUJvbGQnLCAnTW9udHNlcnJhdCc7Zm9udC13ZWlnaHQ6NzAwO2ZvbnQtc2l6ZToxMnB4O2ZpbGw6d2hpdGU7Ij5PcGVuQzx0c3BhbiB4PSIzMTcuOTNweCAiIHk9IjM4MS45NThweCAiPm88L3RzcGFuPmRlPC90ZXh0PgogICAgICAgICAgICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxMiwwLDAsMTIsMzg3LjE1ODM2NywzODEuOTU4MjkyKSI+CiAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPHRleHQgeD0iMzQ0LjgxcHgiIHk9IjM4MS45NThweCIgc3R5bGU9ImZvbnQtZmFtaWx5OidNb250c2VycmF0LVJlZ3VsYXInLCAnTW9udHNlcnJhdCc7Zm9udC1zaXplOjEycHg7ZmlsbDp3aGl0ZTsiPlNwYWM8dHNwYW4geD0iMzc0LjA0MnB4ICIgeT0iMzgxLjk1OHB4ICI+ZTwvdHNwYW4+czwvdGV4dD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPgo=" alt="OpenCode Spaces" />
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div class="terminal">
|
|
495
|
+
<div class="terminal-header">
|
|
496
|
+
<div class="dots">
|
|
497
|
+
<div class="dot dot-red"></div>
|
|
498
|
+
<div class="dot dot-yellow"></div>
|
|
499
|
+
<div class="dot dot-green"></div>
|
|
500
|
+
</div>
|
|
501
|
+
<span class="terminal-title">opencodespaces — cli auth</span>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="terminal-content">
|
|
505
|
+
<div class="line">
|
|
506
|
+
<span class="prompt">$</span>
|
|
507
|
+
<span class="text">opencodespaces login</span>
|
|
508
|
+
</div>
|
|
509
|
+
<div class="line">
|
|
510
|
+
<span class="prompt">></span>
|
|
511
|
+
<span class="text">Opening browser for authentication...</span>
|
|
512
|
+
<span class="check">✓</span>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="line">
|
|
515
|
+
<span class="prompt">></span>
|
|
516
|
+
<span class="text">Verifying credentials...</span>
|
|
517
|
+
<span class="x">✗</span>
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<div class="error-section">
|
|
521
|
+
<div class="error-title">
|
|
522
|
+
<span>⚠</span>
|
|
523
|
+
<span>${displayError}</span>
|
|
524
|
+
</div>
|
|
525
|
+
<p class="error-message">Could not complete authentication. Please try again in your terminal.</p>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<p class="hint">Close this window and run <code>opencodespaces login</code> again.</p>
|
|
531
|
+
</body>
|
|
532
|
+
</html>`;
|
|
533
|
+
}
|
|
18
534
|
/**
|
|
19
535
|
* Find an available port in the range
|
|
20
536
|
*/
|
|
@@ -59,56 +575,26 @@ function startCallbackServer(port) {
|
|
|
59
575
|
const token = url.searchParams.get('token');
|
|
60
576
|
const email = url.searchParams.get('email');
|
|
61
577
|
const name = url.searchParams.get('name');
|
|
578
|
+
const avatar = url.searchParams.get('avatar');
|
|
62
579
|
const error = url.searchParams.get('error');
|
|
63
580
|
// Send response to browser
|
|
64
581
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
65
582
|
if (token) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
p { color: #94a3b8; }
|
|
76
|
-
</style>
|
|
77
|
-
</head>
|
|
78
|
-
<body>
|
|
79
|
-
<div class="container">
|
|
80
|
-
<h1>✓ Login Successful</h1>
|
|
81
|
-
<p>You can close this window and return to your terminal.</p>
|
|
82
|
-
</div>
|
|
83
|
-
</body>
|
|
84
|
-
</html>
|
|
85
|
-
`);
|
|
86
|
-
// Close server after short delay to ensure response is sent
|
|
583
|
+
// Get user initials for avatar fallback
|
|
584
|
+
const initials = (name || 'U')
|
|
585
|
+
.split(' ')
|
|
586
|
+
.map((n) => n[0])
|
|
587
|
+
.join('')
|
|
588
|
+
.toUpperCase()
|
|
589
|
+
.slice(0, 2);
|
|
590
|
+
res.end(generateSuccessPage(name || 'User', email || '', initials, avatar || undefined));
|
|
591
|
+
// Close server after delay to ensure page renders
|
|
87
592
|
setTimeout(() => {
|
|
88
593
|
cleanup({ success: true, token, email: email || undefined, name: name || undefined });
|
|
89
|
-
},
|
|
594
|
+
}, 500);
|
|
90
595
|
}
|
|
91
596
|
else {
|
|
92
|
-
res.end(
|
|
93
|
-
<!DOCTYPE html>
|
|
94
|
-
<html>
|
|
95
|
-
<head>
|
|
96
|
-
<title>OpenCodeSpaces - Login Failed</title>
|
|
97
|
-
<style>
|
|
98
|
-
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0f172a; color: #f1f5f9; }
|
|
99
|
-
.container { text-align: center; padding: 2rem; }
|
|
100
|
-
h1 { color: #ef4444; margin-bottom: 0.5rem; }
|
|
101
|
-
p { color: #94a3b8; }
|
|
102
|
-
</style>
|
|
103
|
-
</head>
|
|
104
|
-
<body>
|
|
105
|
-
<div class="container">
|
|
106
|
-
<h1>✗ Login Failed</h1>
|
|
107
|
-
<p>${error || 'Unknown error occurred'}</p>
|
|
108
|
-
</div>
|
|
109
|
-
</body>
|
|
110
|
-
</html>
|
|
111
|
-
`);
|
|
597
|
+
res.end(generateErrorPage(error || 'Unknown error'));
|
|
112
598
|
setTimeout(() => {
|
|
113
599
|
cleanup({ success: false, error: error || 'Login failed' });
|
|
114
600
|
}, 100);
|