agentgui 1.0.209 → 1.0.211
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/package.json +1 -1
- package/server.js +109 -55
- package/static/js/agent-auth.js +100 -0
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -246,6 +246,9 @@ function extractOAuthFromFile(oauth2Path) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
function getGeminiOAuthCreds() {
|
|
249
|
+
if (process.env.GOOGLE_OAUTH_CLIENT_ID && process.env.GOOGLE_OAUTH_CLIENT_SECRET) {
|
|
250
|
+
return { clientId: process.env.GOOGLE_OAUTH_CLIENT_ID, clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET, custom: true };
|
|
251
|
+
}
|
|
249
252
|
const oauthRelPath = path.join('node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'code_assist', 'oauth2.js');
|
|
250
253
|
try {
|
|
251
254
|
const geminiPath = findCommand('gemini');
|
|
@@ -333,13 +336,24 @@ function geminiOAuthResultPage(title, message, success) {
|
|
|
333
336
|
</div></body></html>`;
|
|
334
337
|
}
|
|
335
338
|
|
|
336
|
-
|
|
339
|
+
function isRemoteRequest(req) {
|
|
340
|
+
return !!(req && (req.headers['x-forwarded-for'] || req.headers['x-forwarded-host'] || req.headers['x-forwarded-proto']));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function startGeminiOAuth(req) {
|
|
337
344
|
const creds = getGeminiOAuthCreds();
|
|
338
345
|
if (!creds) throw new Error('Could not find Gemini CLI OAuth credentials. Install gemini CLI first.');
|
|
339
346
|
|
|
340
|
-
const
|
|
341
|
-
const
|
|
347
|
+
const useCustomClient = !!creds.custom;
|
|
348
|
+
const remote = isRemoteRequest(req);
|
|
349
|
+
let redirectUri;
|
|
350
|
+
if (useCustomClient && req) {
|
|
351
|
+
redirectUri = `${buildBaseUrl(req)}${BASE_URL}/oauth2callback`;
|
|
352
|
+
} else {
|
|
353
|
+
redirectUri = `http://localhost:${PORT}${BASE_URL}/oauth2callback`;
|
|
354
|
+
}
|
|
342
355
|
|
|
356
|
+
const state = crypto.randomBytes(32).toString('hex');
|
|
343
357
|
const client = new OAuth2Client({
|
|
344
358
|
clientId: creds.clientId,
|
|
345
359
|
clientSecret: creds.clientSecret,
|
|
@@ -352,6 +366,7 @@ async function startGeminiOAuth(baseUrl) {
|
|
|
352
366
|
state,
|
|
353
367
|
});
|
|
354
368
|
|
|
369
|
+
const mode = useCustomClient ? 'custom' : (remote ? 'cli-remote' : 'cli-local');
|
|
355
370
|
geminiOAuthPending = { client, redirectUri, state };
|
|
356
371
|
geminiOAuthState = { status: 'pending', error: null, email: null };
|
|
357
372
|
|
|
@@ -362,74 +377,77 @@ async function startGeminiOAuth(baseUrl) {
|
|
|
362
377
|
}
|
|
363
378
|
}, 5 * 60 * 1000);
|
|
364
379
|
|
|
365
|
-
return authUrl;
|
|
380
|
+
return { authUrl, mode };
|
|
366
381
|
}
|
|
367
382
|
|
|
368
|
-
async function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (!geminiOAuthPending) {
|
|
372
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
373
|
-
res.end(geminiOAuthResultPage('Authentication Failed', 'No pending OAuth flow.', false));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const error = reqUrl.searchParams.get('error');
|
|
378
|
-
if (error) {
|
|
379
|
-
const desc = reqUrl.searchParams.get('error_description') || error;
|
|
380
|
-
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
381
|
-
geminiOAuthPending = null;
|
|
382
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
383
|
-
res.end(geminiOAuthResultPage('Authentication Failed', desc, false));
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
383
|
+
async function exchangeGeminiOAuthCode(code, state) {
|
|
384
|
+
if (!geminiOAuthPending) throw new Error('No pending OAuth flow. Please start authentication again.');
|
|
386
385
|
|
|
387
386
|
const { client, redirectUri, state: expectedState } = geminiOAuthPending;
|
|
388
387
|
|
|
389
|
-
if (
|
|
388
|
+
if (state !== expectedState) {
|
|
390
389
|
geminiOAuthState = { status: 'error', error: 'State mismatch', email: null };
|
|
391
390
|
geminiOAuthPending = null;
|
|
392
|
-
|
|
393
|
-
res.end(geminiOAuthResultPage('Authentication Failed', 'State mismatch.', false));
|
|
394
|
-
return;
|
|
391
|
+
throw new Error('State mismatch - possible CSRF attack.');
|
|
395
392
|
}
|
|
396
393
|
|
|
397
|
-
const code = reqUrl.searchParams.get('code');
|
|
398
394
|
if (!code) {
|
|
399
|
-
geminiOAuthState = { status: 'error', error: 'No authorization code', email: null };
|
|
395
|
+
geminiOAuthState = { status: 'error', error: 'No authorization code received', email: null };
|
|
400
396
|
geminiOAuthPending = null;
|
|
397
|
+
throw new Error('No authorization code received.');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const { tokens } = await client.getToken({ code, redirect_uri: redirectUri });
|
|
401
|
+
client.setCredentials(tokens);
|
|
402
|
+
|
|
403
|
+
let email = '';
|
|
404
|
+
try {
|
|
405
|
+
const { token } = await client.getAccessToken();
|
|
406
|
+
if (token) {
|
|
407
|
+
const resp = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
408
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
409
|
+
});
|
|
410
|
+
if (resp.ok) {
|
|
411
|
+
const info = await resp.json();
|
|
412
|
+
email = info.email || '';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (_) {}
|
|
416
|
+
|
|
417
|
+
saveGeminiCredentials(tokens, email);
|
|
418
|
+
geminiOAuthState = { status: 'success', error: null, email };
|
|
419
|
+
geminiOAuthPending = null;
|
|
420
|
+
|
|
421
|
+
return email;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function handleGeminiOAuthCallback(req, res) {
|
|
425
|
+
const reqUrl = new URL(req.url, `http://localhost:${PORT}`);
|
|
426
|
+
|
|
427
|
+
if (!geminiOAuthPending) {
|
|
401
428
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
402
|
-
res.end(geminiOAuthResultPage('Authentication Failed', 'No
|
|
429
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'No pending OAuth flow.', false));
|
|
403
430
|
return;
|
|
404
431
|
}
|
|
405
432
|
|
|
406
433
|
try {
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
});
|
|
417
|
-
if (resp.ok) {
|
|
418
|
-
const info = await resp.json();
|
|
419
|
-
email = info.email || '';
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
} catch (_) {}
|
|
434
|
+
const error = reqUrl.searchParams.get('error');
|
|
435
|
+
if (error) {
|
|
436
|
+
const desc = reqUrl.searchParams.get('error_description') || error;
|
|
437
|
+
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
438
|
+
geminiOAuthPending = null;
|
|
439
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
440
|
+
res.end(geminiOAuthResultPage('Authentication Failed', desc, false));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
423
443
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
444
|
+
const code = reqUrl.searchParams.get('code');
|
|
445
|
+
const state = reqUrl.searchParams.get('state');
|
|
446
|
+
const email = await exchangeGeminiOAuthCode(code, state);
|
|
427
447
|
|
|
428
448
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
429
449
|
res.end(geminiOAuthResultPage('Authentication Successful', email ? `Signed in as ${email}` : 'Gemini CLI credentials saved.', true));
|
|
430
450
|
} catch (e) {
|
|
431
|
-
geminiOAuthState = { status: 'error', error: e.message, email: null };
|
|
432
|
-
geminiOAuthPending = null;
|
|
433
451
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
434
452
|
res.end(geminiOAuthResultPage('Authentication Failed', e.message, false));
|
|
435
453
|
}
|
|
@@ -1127,8 +1145,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
1127
1145
|
|
|
1128
1146
|
if (pathOnly === '/api/gemini-oauth/start' && req.method === 'POST') {
|
|
1129
1147
|
try {
|
|
1130
|
-
const
|
|
1131
|
-
sendJSON(req, res, 200, { authUrl });
|
|
1148
|
+
const result = await startGeminiOAuth(req);
|
|
1149
|
+
sendJSON(req, res, 200, { authUrl: result.authUrl, mode: result.mode });
|
|
1132
1150
|
} catch (e) {
|
|
1133
1151
|
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
1134
1152
|
sendJSON(req, res, 500, { error: e.message });
|
|
@@ -1141,6 +1159,42 @@ const server = http.createServer(async (req, res) => {
|
|
|
1141
1159
|
return;
|
|
1142
1160
|
}
|
|
1143
1161
|
|
|
1162
|
+
if (pathOnly === '/api/gemini-oauth/complete' && req.method === 'POST') {
|
|
1163
|
+
try {
|
|
1164
|
+
const body = await parseBody(req);
|
|
1165
|
+
const pastedUrl = (body.url || '').trim();
|
|
1166
|
+
if (!pastedUrl) {
|
|
1167
|
+
sendJSON(req, res, 400, { error: 'No URL provided' });
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
let parsed;
|
|
1172
|
+
try { parsed = new URL(pastedUrl); } catch (_) {
|
|
1173
|
+
sendJSON(req, res, 400, { error: 'Invalid URL. Paste the full URL from the browser address bar.' });
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const error = parsed.searchParams.get('error');
|
|
1178
|
+
if (error) {
|
|
1179
|
+
const desc = parsed.searchParams.get('error_description') || error;
|
|
1180
|
+
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
1181
|
+
geminiOAuthPending = null;
|
|
1182
|
+
sendJSON(req, res, 200, { error: desc });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const code = parsed.searchParams.get('code');
|
|
1187
|
+
const state = parsed.searchParams.get('state');
|
|
1188
|
+
const email = await exchangeGeminiOAuthCode(code, state);
|
|
1189
|
+
sendJSON(req, res, 200, { success: true, email });
|
|
1190
|
+
} catch (e) {
|
|
1191
|
+
geminiOAuthState = { status: 'error', error: e.message, email: null };
|
|
1192
|
+
geminiOAuthPending = null;
|
|
1193
|
+
sendJSON(req, res, 400, { error: e.message });
|
|
1194
|
+
}
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1144
1198
|
const agentAuthMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/auth$/);
|
|
1145
1199
|
if (agentAuthMatch && req.method === 'POST') {
|
|
1146
1200
|
const agentId = agentAuthMatch[1];
|
|
@@ -1149,10 +1203,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
1149
1203
|
|
|
1150
1204
|
if (agentId === 'gemini') {
|
|
1151
1205
|
try {
|
|
1152
|
-
const
|
|
1206
|
+
const result = await startGeminiOAuth(req);
|
|
1153
1207
|
const conversationId = '__agent_auth__';
|
|
1154
1208
|
broadcastSync({ type: 'script_started', conversationId, script: 'auth-gemini', agentId: 'gemini', timestamp: Date.now() });
|
|
1155
|
-
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1209
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${result.authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1156
1210
|
|
|
1157
1211
|
const pollId = setInterval(() => {
|
|
1158
1212
|
if (geminiOAuthState.status === 'success') {
|
|
@@ -1169,7 +1223,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1169
1223
|
|
|
1170
1224
|
setTimeout(() => clearInterval(pollId), 5 * 60 * 1000);
|
|
1171
1225
|
|
|
1172
|
-
sendJSON(req, res, 200, { ok: true, agentId, authUrl });
|
|
1226
|
+
sendJSON(req, res, 200, { ok: true, agentId, authUrl: result.authUrl, mode: result.mode });
|
|
1173
1227
|
return;
|
|
1174
1228
|
} catch (e) {
|
|
1175
1229
|
console.error('[gemini-oauth] /api/agents/gemini/auth failed:', e);
|
package/static/js/agent-auth.js
CHANGED
|
@@ -130,6 +130,81 @@
|
|
|
130
130
|
|
|
131
131
|
function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
|
|
132
132
|
|
|
133
|
+
var oauthPollInterval = null, oauthPollTimeout = null;
|
|
134
|
+
|
|
135
|
+
function cleanupOAuthPolling() {
|
|
136
|
+
if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
|
|
137
|
+
if (oauthPollTimeout) { clearTimeout(oauthPollTimeout); oauthPollTimeout = null; }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function showOAuthPasteModal() {
|
|
141
|
+
removeOAuthPasteModal();
|
|
142
|
+
var overlay = document.createElement('div');
|
|
143
|
+
overlay.id = 'oauthPasteModal';
|
|
144
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
145
|
+
var s = function(c) { return 'font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 ' + (c ? '0' : '0.5rem') + ';'; };
|
|
146
|
+
overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:28rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
|
|
147
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">' +
|
|
148
|
+
'<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Complete Google Sign-In</h2>' +
|
|
149
|
+
'<button id="oauthPasteClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
|
|
150
|
+
'<div style="margin-bottom:1rem;padding:1rem;background:var(--color-bg-tertiary,rgba(255,255,255,0.05));border-radius:0.5rem;">' +
|
|
151
|
+
'<p style="' + s() + '">1. A Google sign-in page has opened in a new tab.</p>' +
|
|
152
|
+
'<p style="' + s() + '">2. Complete the sign-in process with Google.</p>' +
|
|
153
|
+
'<p style="' + s() + '">3. After signing in, you will be redirected to a page that <span style="color:#facc15;font-weight:600;">may not load</span> (this is expected).</p>' +
|
|
154
|
+
'<p style="' + s(1) + '">4. Copy the <span style="color:white;font-weight:600;">entire URL</span> from the address bar and paste it below.</p></div>' +
|
|
155
|
+
'<label style="display:block;font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin-bottom:0.5rem;">Paste the redirect URL here:</label>' +
|
|
156
|
+
'<input type="text" id="oauthPasteInput" placeholder="http://localhost:3000/gm/oauth2callback?code=..." style="width:100%;box-sizing:border-box;padding:0.75rem 1rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.5rem;color:var(--color-text-primary,white);font-size:0.8rem;font-family:monospace;outline:none;" />' +
|
|
157
|
+
'<p id="oauthPasteError" style="font-size:0.75rem;color:#ef4444;margin:0.5rem 0 0;display:none;"></p>' +
|
|
158
|
+
'<div style="display:flex;gap:0.75rem;margin-top:1.25rem;">' +
|
|
159
|
+
'<button id="oauthPasteCancel" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button>' +
|
|
160
|
+
'<button id="oauthPasteSubmit" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:none;background:var(--color-primary,#3b82f6);color:white;font-size:0.8rem;cursor:pointer;font-weight:600;">Complete Sign-In</button></div>' +
|
|
161
|
+
'<p style="font-size:0.7rem;color:var(--color-text-secondary,#6b7280);margin-top:1rem;text-align:center;">If the redirect page loaded successfully, this dialog will close automatically.</p></div>';
|
|
162
|
+
document.body.appendChild(overlay);
|
|
163
|
+
var dismiss = function() { cleanupOAuthPolling(); authRunning = false; removeOAuthPasteModal(); };
|
|
164
|
+
document.getElementById('oauthPasteClose').addEventListener('click', dismiss);
|
|
165
|
+
document.getElementById('oauthPasteCancel').addEventListener('click', dismiss);
|
|
166
|
+
document.getElementById('oauthPasteSubmit').addEventListener('click', submitOAuthPasteUrl);
|
|
167
|
+
document.getElementById('oauthPasteInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') submitOAuthPasteUrl(); });
|
|
168
|
+
setTimeout(function() { var i = document.getElementById('oauthPasteInput'); if (i) i.focus(); }, 100);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function removeOAuthPasteModal() {
|
|
172
|
+
var el = document.getElementById('oauthPasteModal');
|
|
173
|
+
if (el) el.remove();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function submitOAuthPasteUrl() {
|
|
177
|
+
var input = document.getElementById('oauthPasteInput');
|
|
178
|
+
var errorEl = document.getElementById('oauthPasteError');
|
|
179
|
+
var submitBtn = document.getElementById('oauthPasteSubmit');
|
|
180
|
+
if (!input) return;
|
|
181
|
+
var url = input.value.trim();
|
|
182
|
+
if (!url) {
|
|
183
|
+
if (errorEl) { errorEl.textContent = 'Please paste the URL from the redirected page.'; errorEl.style.display = 'block'; }
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
|
|
187
|
+
if (errorEl) errorEl.style.display = 'none';
|
|
188
|
+
|
|
189
|
+
fetch(BASE + '/api/gemini-oauth/complete', {
|
|
190
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
191
|
+
body: JSON.stringify({ url: url })
|
|
192
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
193
|
+
if (data.success) {
|
|
194
|
+
cleanupOAuthPolling();
|
|
195
|
+
authRunning = false;
|
|
196
|
+
removeOAuthPasteModal();
|
|
197
|
+
refresh();
|
|
198
|
+
} else {
|
|
199
|
+
if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
|
|
200
|
+
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
201
|
+
}
|
|
202
|
+
}).catch(function(e) {
|
|
203
|
+
if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
|
|
204
|
+
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
133
208
|
function triggerAuth(agentId) {
|
|
134
209
|
if (authRunning) return;
|
|
135
210
|
fetch(BASE + '/api/agents/' + agentId + '/auth', {
|
|
@@ -141,6 +216,29 @@
|
|
|
141
216
|
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
|
|
142
217
|
if (data.authUrl) {
|
|
143
218
|
window.open(data.authUrl, '_blank');
|
|
219
|
+
if (agentId === 'gemini') {
|
|
220
|
+
var needsPaste = data.mode === 'cli-remote';
|
|
221
|
+
if (needsPaste) showOAuthPasteModal();
|
|
222
|
+
cleanupOAuthPolling();
|
|
223
|
+
oauthPollInterval = setInterval(function() {
|
|
224
|
+
fetch(BASE + '/api/gemini-oauth/status').then(function(r) { return r.json(); }).then(function(status) {
|
|
225
|
+
if (status.status === 'success') {
|
|
226
|
+
cleanupOAuthPolling();
|
|
227
|
+
authRunning = false;
|
|
228
|
+
removeOAuthPasteModal();
|
|
229
|
+
refresh();
|
|
230
|
+
} else if (status.status === 'error') {
|
|
231
|
+
cleanupOAuthPolling();
|
|
232
|
+
authRunning = false;
|
|
233
|
+
removeOAuthPasteModal();
|
|
234
|
+
}
|
|
235
|
+
}).catch(function() {});
|
|
236
|
+
}, 1500);
|
|
237
|
+
oauthPollTimeout = setTimeout(function() {
|
|
238
|
+
cleanupOAuthPolling();
|
|
239
|
+
if (authRunning) { authRunning = false; removeOAuthPasteModal(); }
|
|
240
|
+
}, 5 * 60 * 1000);
|
|
241
|
+
}
|
|
144
242
|
}
|
|
145
243
|
}
|
|
146
244
|
}).catch(function() {});
|
|
@@ -159,6 +257,8 @@
|
|
|
159
257
|
if (term) term.write(data.data);
|
|
160
258
|
} else if (data.type === 'script_stopped') {
|
|
161
259
|
authRunning = false;
|
|
260
|
+
removeOAuthPasteModal();
|
|
261
|
+
cleanupOAuthPolling();
|
|
162
262
|
var term = getTerminal();
|
|
163
263
|
var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
|
|
164
264
|
if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
|