switchman-dev 0.1.8 → 0.1.10

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.
@@ -223,15 +223,7 @@ async function refreshToken(refreshToken) {
223
223
  * Returns { success, email } or { success: false, error }
224
224
  */
225
225
  export async function loginWithGitHub() {
226
- // Use Supabase's PKCE/implicit flow by opening the browser
227
- // with a special device-style URL that redirects to a local callback
228
226
  const { default: open } = await import('open');
229
-
230
- // We use a simple approach: direct the user to the Pro page sign-in
231
- // which sets the session, then we poll Supabase for the session
232
- // using a one-time code approach via the CLI callback server
233
-
234
- // Start a tiny local HTTP server to catch the OAuth callback
235
227
  const { createServer } = await import('http');
236
228
 
237
229
  return new Promise((resolve) => {
@@ -239,59 +231,95 @@ export async function loginWithGitHub() {
239
231
  const timeout = setTimeout(() => {
240
232
  server?.close();
241
233
  resolve({ success: false, error: 'timeout' });
242
- }, 5 * 60 * 1000); // 5 minute timeout
234
+ }, 5 * 60 * 1000);
243
235
 
244
236
  server = createServer(async (req, res) => {
245
237
  const url = new URL(req.url, 'http://localhost:7429');
246
238
 
247
239
  if (url.pathname === '/callback') {
248
- const code = url.searchParams.get('code');
249
- const error = url.searchParams.get('error');
240
+ const code = url.searchParams.get('code');
241
+ const accessToken = url.searchParams.get('access_token');
242
+ const refreshToken = url.searchParams.get('refresh_token');
243
+ const error = url.searchParams.get('error');
250
244
 
251
245
  res.writeHead(200, { 'Content-Type': 'text/html' });
252
246
  res.end(`
253
- <!DOCTYPE html>
254
- <html>
247
+ <!DOCTYPE html><html>
255
248
  <head><style>
256
- body { background: #0b1020; color: #e6eef8; font-family: monospace;
257
- display: flex; align-items: center; justify-content: center;
258
- min-height: 100vh; margin: 0; }
259
- .box { text-align: center; }
260
- .ok { color: #4ade80; font-size: 48px; }
261
- h2 { font-size: 24px; margin: 16px 0 8px; }
262
- p { color: #5f7189; }
249
+ body { background:#0b1020; color:#e6eef8; font-family:monospace;
250
+ display:flex; align-items:center; justify-content:center;
251
+ min-height:100vh; margin:0; }
252
+ .box { text-align:center; }
253
+ .ok { color:#4ade80; font-size:48px; }
254
+ .err { color:#f87171; font-size:48px; }
255
+ h2 { font-size:24px; margin:16px 0 8px; }
256
+ p { color:#5f7189; }
263
257
  </style></head>
264
- <body>
265
- <div class="box">
266
- <div class="ok">${error ? '' : ''}</div>
267
- <h2>${error ? 'Sign in failed' : 'Signed in successfully'}</h2>
268
- <p>${error ? 'You can close this tab.' : 'You can close this tab and return to your terminal.'}</p>
269
- </div>
270
- </body>
271
- </html>
258
+ <body><div class="box">
259
+ <div class="${error ? 'err' : 'ok'}">${error ? '✕' : '✓'}</div>
260
+ <h2>${error ? 'Sign in failed' : 'Signed in successfully'}</h2>
261
+ <p>${error ? 'You can close this tab.' : 'You can close this tab and return to your terminal.'}</p>
262
+ </div></body></html>
272
263
  `);
273
264
 
274
265
  clearTimeout(timeout);
275
266
  server.close();
276
267
 
277
- if (error || !code) {
278
- resolve({ success: false, error: error || 'no_code' });
268
+ if (error) {
269
+ resolve({ success: false, error });
279
270
  return;
280
271
  }
281
272
 
282
- // Exchange the code for a session via Supabase
283
- try {
284
- const tokenRes = await fetch(`${AUTH_URL}/token?grant_type=pkce`, {
285
- method: 'POST',
286
- headers: {
287
- 'Content-Type': 'application/json',
288
- 'apikey': SUPABASE_ANON,
289
- },
290
- body: JSON.stringify({ auth_code: code }),
273
+ // If Supabase sent the token directly as query params
274
+ if (accessToken) {
275
+ saveSession({
276
+ access_token: accessToken,
277
+ refresh_token: refreshToken ?? null,
278
+ expires_in: 3600,
279
+ user: null, // will be fetched on next checkLicence
291
280
  });
281
+ // Fetch the user email from Supabase
282
+ try {
283
+ const userRes = await fetch(`${AUTH_URL}/user`, {
284
+ headers: {
285
+ 'Authorization': `Bearer ${accessToken}`,
286
+ 'apikey': SUPABASE_ANON,
287
+ },
288
+ });
289
+ if (userRes.ok) {
290
+ const user = await userRes.json();
291
+ const creds = readCredentials() || {};
292
+ writeCredentials({ ...creds, email: user.email, user_id: user.id });
293
+ resolve({ success: true, email: user.email });
294
+ } else {
295
+ resolve({ success: true, email: null });
296
+ }
297
+ } catch {
298
+ resolve({ success: true, email: null });
299
+ }
300
+ return;
301
+ }
292
302
 
293
- if (!tokenRes.ok) {
294
- // Try the standard exchange endpoint
303
+ // Exchange the code for a session
304
+ if (code) {
305
+ try {
306
+ const tokenRes = await fetch(`${AUTH_URL}/token?grant_type=pkce`, {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ 'apikey': SUPABASE_ANON,
311
+ },
312
+ body: JSON.stringify({ auth_code: code }),
313
+ });
314
+
315
+ if (tokenRes.ok) {
316
+ const session = await tokenRes.json();
317
+ saveSession(session);
318
+ resolve({ success: true, email: session.user?.email ?? null });
319
+ return;
320
+ }
321
+
322
+ // Fallback exchange
295
323
  const exchangeRes = await fetch(`${AUTH_URL}/token?grant_type=authorization_code`, {
296
324
  method: 'POST',
297
325
  headers: {
@@ -301,32 +329,29 @@ export async function loginWithGitHub() {
301
329
  body: JSON.stringify({ code }),
302
330
  });
303
331
 
304
- if (!exchangeRes.ok) {
305
- resolve({ success: false, error: 'token_exchange_failed' });
332
+ if (exchangeRes.ok) {
333
+ const session = await exchangeRes.json();
334
+ saveSession(session);
335
+ resolve({ success: true, email: session.user?.email ?? null });
306
336
  return;
307
337
  }
308
338
 
309
- const session = await exchangeRes.json();
310
- saveSession(session);
311
- resolve({ success: true, email: session.user?.email });
312
- return;
339
+ resolve({ success: false, error: 'token_exchange_failed' });
340
+ } catch (err) {
341
+ resolve({ success: false, error: err.message });
313
342
  }
314
-
315
- const session = await tokenRes.json();
316
- saveSession(session);
317
- resolve({ success: true, email: session.user?.email });
318
- } catch (err) {
319
- resolve({ success: false, error: err.message });
343
+ return;
320
344
  }
345
+
346
+ resolve({ success: false, error: 'no_code' });
321
347
  }
322
348
  });
323
349
 
324
350
  server.listen(7429, 'localhost', () => {
325
- // Build the Supabase GitHub OAuth URL with our local callback
326
351
  const params = new URLSearchParams({
327
- provider: 'github',
352
+ provider: 'github',
328
353
  redirect_to: 'http://localhost:7429/callback',
329
- scopes: 'read:user user:email',
354
+ scopes: 'read:user user:email',
330
355
  });
331
356
 
332
357
  const loginUrl = `${AUTH_URL}/authorize?${params}`;
@@ -336,7 +361,7 @@ export async function loginWithGitHub() {
336
361
  console.log('');
337
362
 
338
363
  open(loginUrl).catch(() => {
339
- console.log(` Could not open browser automatically.`);
364
+ console.log(' Could not open browser automatically.');
340
365
  console.log(` Please visit: ${loginUrl}`);
341
366
  });
342
367
  });
package/src.zip ADDED
Binary file
package/tests.zip DELETED
Binary file