@livedesk/client 0.1.10 → 0.1.12

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.
@@ -271,16 +271,86 @@ async function createSupabaseClient() {
271
271
 
272
272
  function openBrowser(url) {
273
273
  if (os.platform() === 'win32') {
274
- spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
274
+ spawn('rundll32.exe', ['url.dll,FileProtocolHandler', url], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
275
275
  return;
276
276
  }
277
277
  const command = os.platform() === 'darwin' ? 'open' : 'xdg-open';
278
278
  spawn(command, [url], { detached: true, stdio: 'ignore' }).unref();
279
279
  }
280
280
 
281
+ function escapeHtml(value) {
282
+ return String(value ?? '')
283
+ .replaceAll('&', '&')
284
+ .replaceAll('<', '&lt;')
285
+ .replaceAll('>', '&gt;')
286
+ .replaceAll('"', '&quot;')
287
+ .replaceAll("'", '&#39;');
288
+ }
289
+
290
+ function renderOAuthCallbackPage({ title, message, tone = 'neutral' }) {
291
+ const accent = tone === 'error' ? '#991b1b' : tone === 'waiting' ? '#374151' : '#111827';
292
+ return `<!doctype html>
293
+ <html lang="en">
294
+ <head>
295
+ <meta charset="utf-8">
296
+ <meta name="viewport" content="width=device-width, initial-scale=1">
297
+ <title>${escapeHtml(title)}</title>
298
+ <style>
299
+ :root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
300
+ * { box-sizing: border-box; }
301
+ html, body { width: 100%; min-height: 100%; margin: 0; }
302
+ body {
303
+ display: grid;
304
+ place-items: center;
305
+ padding: 24px;
306
+ background:
307
+ radial-gradient(circle at 50% 0%, rgba(148, 163, 184, 0.22), transparent 34%),
308
+ linear-gradient(180deg, #f8fafc, #eef2f7);
309
+ color: #111827;
310
+ }
311
+ main {
312
+ width: min(440px, 100%);
313
+ display: grid;
314
+ gap: 14px;
315
+ padding: 28px;
316
+ border: 1px solid rgba(148, 163, 184, 0.34);
317
+ border-radius: 12px;
318
+ background: rgba(255, 255, 255, 0.9);
319
+ box-shadow: 0 24px 70px rgba(15, 23, 42, 0.14);
320
+ text-align: center;
321
+ }
322
+ .mark {
323
+ width: 42px;
324
+ height: 42px;
325
+ display: grid;
326
+ place-items: center;
327
+ justify-self: center;
328
+ border-radius: 10px;
329
+ background: ${accent};
330
+ color: #ffffff;
331
+ font-weight: 950;
332
+ }
333
+ h1 { margin: 0; font-size: 28px; line-height: 1.05; letter-spacing: 0; }
334
+ p { margin: 0; color: #64748b; font-size: 15px; line-height: 1.5; font-weight: 650; }
335
+ small { color: #94a3b8; font-size: 12px; font-weight: 750; }
336
+ </style>
337
+ </head>
338
+ <body>
339
+ <main>
340
+ <div class="mark">LD</div>
341
+ <h1>${escapeHtml(title)}</h1>
342
+ <p>${escapeHtml(message)}</p>
343
+ <small>LiveDesk Client</small>
344
+ </main>
345
+ </body>
346
+ </html>`;
347
+ }
348
+
281
349
  async function startOAuthCallbackServer(options = {}) {
282
350
  const host = String(process.env.LIVEDESK_CLIENT_AUTH_HOST || DEFAULT_AUTH_CALLBACK_HOST).trim() || DEFAULT_AUTH_CALLBACK_HOST;
283
351
  const port = normalizePort(options.authPort) || DEFAULT_AUTH_CALLBACK_PORT;
352
+ let listeningPort = port;
353
+ let completed = false;
284
354
  let settleCode;
285
355
  let rejectCode;
286
356
  const waitForCode = new Promise((resolve, reject) => {
@@ -288,23 +358,49 @@ async function startOAuthCallbackServer(options = {}) {
288
358
  rejectCode = reject;
289
359
  });
290
360
  const server = createServer((req, res) => {
291
- const requestUrl = new URL(req.url || '/', `http://127.0.0.1:${server.address().port}`);
361
+ const requestUrl = new URL(req.url || '/', `http://${host}:${listeningPort}`);
362
+ if (requestUrl.pathname === '/favicon.ico') {
363
+ res.writeHead(204);
364
+ res.end();
365
+ return;
366
+ }
367
+ if (completed) {
368
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
369
+ res.end(renderOAuthCallbackPage({
370
+ title: 'LiveDesk sign-in complete',
371
+ message: 'You can close this tab and return to the terminal.'
372
+ }));
373
+ return;
374
+ }
292
375
  const code = requestUrl.searchParams.get('code');
293
376
  const error = requestUrl.searchParams.get('error_description') || requestUrl.searchParams.get('error');
294
377
  if (error) {
295
378
  res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
296
- res.end('<h1>LiveDesk sign-in failed</h1><p>You can close this tab.</p>');
379
+ res.end(renderOAuthCallbackPage({
380
+ title: 'LiveDesk sign-in failed',
381
+ message: error,
382
+ tone: 'error'
383
+ }));
384
+ completed = true;
297
385
  rejectCode(new Error(error));
298
386
  server.close();
299
387
  return;
300
388
  }
301
389
  if (!code) {
302
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
303
- res.end('LiveDesk sign-in callback is waiting for an auth code.');
390
+ res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
391
+ res.end(renderOAuthCallbackPage({
392
+ title: 'Waiting for LiveDesk sign-in',
393
+ message: 'This local callback is waiting for an auth code from Google.',
394
+ tone: 'waiting'
395
+ }));
304
396
  return;
305
397
  }
306
398
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
307
- res.end('<h1>LiveDesk sign-in complete</h1><p>You can close this tab and return to the terminal.</p>');
399
+ res.end(renderOAuthCallbackPage({
400
+ title: 'LiveDesk sign-in complete',
401
+ message: 'You can close this tab and return to the terminal.'
402
+ }));
403
+ completed = true;
308
404
  settleCode(code);
309
405
  server.close();
310
406
  });
@@ -313,6 +409,8 @@ async function startOAuthCallbackServer(options = {}) {
313
409
  server.once('error', reject);
314
410
  server.listen(port, host, resolve);
315
411
  });
412
+ const address = server.address();
413
+ listeningPort = typeof address === 'object' && address ? address.port : port;
316
414
  } catch (err) {
317
415
  if (err?.code === 'EADDRINUSE') {
318
416
  throw new Error(`LiveDesk Google sign-in callback port ${port} is already in use. Close the app using it or run with --auth-port <port> and add that callback URL in Supabase Auth redirect URLs.`);
@@ -321,7 +419,7 @@ async function startOAuthCallbackServer(options = {}) {
321
419
  }
322
420
  return {
323
421
  get redirectTo() {
324
- return `http://${host}:${server.address().port}/callback`;
422
+ return `http://${host}:${listeningPort}/callback`;
325
423
  },
326
424
  waitForCode
327
425
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {