Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix Auth0 login: cleanAuth0Params ordering, /user fetch timeout, bios queue race

- cleanAuth0Params deleted `state` before checking it, so `code` was
never removed from the URL after OAuth redirect
- /user fetch had no timeout — cold starts could hang indefinitely,
blocking session:started from ever being sent to the disk worker
- bios.mjs diskSends queue was module-scoped, so when WS + HTTP imports
raced and created two module instances, consumeDiskSends could flush
the wrong (empty) array while session:started sat in the other

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+39 -22
+13 -11
system/public/aesthetic.computer/bios.mjs
··· 651 651 window.acDISABLE_HUD_LABEL_CACHE = true; // Disable HUD label caching for dynamic coloring 652 652 window.acDISABLE_QR_OVERLAY_CACHE = true; // Disable QR overlay caching for dynamic updates 653 653 654 - const diskSends = []; 655 - let diskSendsConsumed = false; 654 + // Guard: When WS + HTTP module imports race (importWithRetry), both module 655 + // instances execute top-level code. Use a window-scoped queue so ALL instances 656 + // share the same sends array and consumed flag — prevents session:started from 657 + // being pushed to one instance's array but consumed from another (empty) one. 658 + if (!window._acDiskSendQ) { 659 + window._acDiskSendQ = { sends: [], consumed: false }; 660 + } 661 + const _diskQ = window._acDiskSendQ; 656 662 657 663 // Store original URL parameters for refresh functionality 658 664 let preservedParams = {}; 659 665 660 - // Guard: When WS + HTTP module imports race (importWithRetry), both module 661 - // instances execute top-level code. The second overwrites window.acDISK_SEND 662 - // with its own diskSends array while boot() uses the first module's 663 - // consumeDiskSends — splitting the queue so session:started never arrives. 664 666 if (!window.acDISK_SEND) { 665 667 window.acDISK_SEND = function (message) { 666 - !diskSendsConsumed ? diskSends.push(message) : window.acSEND(message); 668 + !_diskQ.consumed ? _diskQ.sends.push(message) : window.acSEND(message); 667 669 }; 668 670 } 669 671 ··· 727 729 }); 728 730 729 731 function consumeDiskSends(send) { 730 - if (diskSendsConsumed) return; 731 - diskSends.forEach((message) => send(message)); 732 - diskSends.length = 0; 733 - diskSendsConsumed = true; 732 + if (_diskQ.consumed) return; 733 + _diskQ.sends.forEach((message) => send(message)); 734 + _diskQ.sends.length = 0; 735 + _diskQ.consumed = true; 734 736 } 735 737 736 738 // 🔌 USB
+26 -11
system/public/aesthetic.computer/boot.mjs
··· 521 521 // Remove Auth0 parameters from URL 522 522 function cleanAuth0Params(url) { 523 523 const urlObj = new URL(url); 524 - 524 + 525 + // Check for Auth0 callback BEFORE removing parameters 526 + const isAuth0Callback = urlObj.searchParams.has('state'); 527 + 525 528 // Always remove these Auth0 parameters 526 529 AUTH0_PARAMS.forEach(param => { 527 530 urlObj.searchParams.delete(param); 528 531 }); 529 - 530 - // Only remove 'code' if 'state' is also present (indicating Auth0 callback) 531 - if (urlObj.searchParams.has('state')) { 532 + 533 + // Remove 'code' if this was an Auth0 callback 534 + if (isAuth0Callback) { 532 535 urlObj.searchParams.delete('code'); 533 536 } 534 - 537 + 535 538 return urlObj.pathname + (urlObj.searchParams.toString() ? '?' + urlObj.searchParams.toString() : ''); 536 539 } 537 540 ··· 1444 1447 params.delete("session-sotce"); 1445 1448 1446 1449 // Also ensure any remaining Auth0 parameters are cleaned 1450 + const hadState = params.has('state'); 1447 1451 AUTH0_PARAMS.forEach(param => params.delete(param)); 1448 - // Only remove 'code' if this appears to be an Auth0 callback 1449 - if (params.has('state')) { 1452 + // Only remove 'code' if this was an Auth0 callback 1453 + if (hadState) { 1450 1454 params.delete('code'); 1451 1455 } 1452 1456 ··· 1535 1539 window.acAuthTiming.getUserEnd = performance.now(); 1536 1540 bootLog(`auth0 getUser (${Math.round(window.acAuthTiming.getUserEnd - window.acAuthTiming.getUserStart)}ms)`); 1537 1541 window.acAuthTiming.userExistsFetchStart = performance.now(); 1538 - const userExists = await fetch( 1539 - `/user?from=${encodeURIComponent(userProfile.email)}&tenant=aesthetic&withHandle=true`, 1540 - ); 1541 - const u = await userExists.json(); 1542 + // Timeout the /user fetch to prevent indefinite hangs on cold starts 1543 + const userFetchController = new AbortController(); 1544 + const userFetchTimeout = setTimeout(() => userFetchController.abort(), 8000); 1545 + let u = {}; 1546 + try { 1547 + const userExists = await fetch( 1548 + `/user?from=${encodeURIComponent(userProfile.email)}&tenant=aesthetic&withHandle=true`, 1549 + { signal: userFetchController.signal }, 1550 + ); 1551 + u = await userExists.json(); 1552 + } catch (err) { 1553 + console.warn("🔐 /user fetch failed or timed out:", err.name); 1554 + } finally { 1555 + clearTimeout(userFetchTimeout); 1556 + } 1542 1557 window.acAuthTiming.userExistsFetchEnd = performance.now(); 1543 1558 bootLog(`userExists fetch (${Math.round(window.acAuthTiming.userExistsFetchEnd - window.acAuthTiming.userExistsFetchStart)}ms)`); 1544 1559 if (!u.sub || !userProfile.email_verified) {