Monorepo for Aesthetic.Computer aesthetic.computer
at main 215 lines 6.5 kB view raw
1(function () { 2 const vscode = acquireVsCodeApi(); 3 4 // Debug logging is opt-in to avoid flooding the devtools console. 5 // Enable by running in the webview devtools console: 6 // localStorage.setItem('ac:webviewDebug','1') 7 // and then reloading the webview. 8 const DEBUG = (() => { 9 try { 10 return localStorage.getItem('ac:webviewDebug') === '1'; 11 } catch { 12 return false; 13 } 14 })(); 15 16 // Send session data to iframe 17 const iframe = document.getElementById("aesthetic"); 18 19 iframe.classList.add("visible"); 20 21 // ♻️ Iframe ready state tracking (declared early for message handler access) 22 let readyTimeout = null; 23 let loadTimeout = null; 24 let refreshCount = 0; 25 let isReady = false; 26 const maxRefreshAttempts = 5; 27 28 // Helper to clear all pending timeouts 29 function clearAllTimeouts() { 30 if (readyTimeout) { 31 clearTimeout(readyTimeout); 32 readyTimeout = null; 33 } 34 if (loadTimeout) { 35 clearTimeout(loadTimeout); 36 loadTimeout = null; 37 } 38 } 39 40 // Handle messages sent from the extension to the webview AND from the iframe 41 window.addEventListener("message", (event) => { 42 const message = event.data; // The json data that the extension sent 43 if (DEBUG) { 44 // Avoid flooding logs with boot chatter unless debugging. 45 if (message?.type !== 'boot-log') { 46 console.log( 47 "📶 Message:", 48 message, 49 "from:", 50 event.source === iframe.contentWindow ? "iframe" : "extension", 51 ); 52 } 53 } 54 switch (message.type) { 55 case "vscode-extension:reload": { 56 vscode.postMessage({ type: "vscode-extension:reload" }); 57 break; 58 } 59 case "vscode-extension:defocus": { 60 vscode.postMessage({ type: "vscode-extension:defocus" }); 61 break; 62 } 63 case "setSession": { 64 // window.aestheticSession = message.session; 65 // sendSessionToIframe(message.session); 66 break; 67 } 68 case "clipboard:copy": { 69 // console.log("📎 Copying clipboard message..."); 70 vscode.postMessage({ type: "clipboard:copy", value: message.value }); 71 break; 72 } 73 case "url:updated": { 74 vscode.postMessage({ type: "url:updated", slug: message.slug }); 75 break; 76 } 77 case "clipboard:copy:confirmation": { 78 iframe.contentWindow.postMessage( 79 { type: "clipboard:copy:confirmation" }, 80 "*", 81 ); 82 break; 83 } 84 // case "aesthetic-panel:open": { 85 // console.log("🪧 Posting panel opener..."); 86 // iframe.contentWindow.postMessage({ type: "aesthetic-panel:open" }, "*"); 87 // break; 88 // } 89 case "publish": { 90 vscode.postMessage({ 91 type: "publish", 92 url: message.url, 93 }); 94 break; 95 } 96 case "setCode": { 97 vscode.postMessage({ type: "setCode", value: message.value }); 98 break; 99 } 100 case "runPiece": { 101 vscode.postMessage({ type: "runPiece" }); 102 break; 103 } 104 case "openDocs": { 105 vscode.postMessage({ type: "openDocs" }); 106 break; 107 } 108 case "openSource": { 109 vscode.postMessage({ 110 type: "openSource", 111 title: message.title, 112 source: message.source, 113 }); 114 break; 115 } 116 case "login": { 117 vscode.postMessage({ 118 type: "login", 119 tenant: message.tenant || "aesthetic", 120 }); 121 break; 122 } 123 case "logout": { 124 vscode.postMessage({ 125 type: "logout", 126 tenant: message.tenant || "aesthetic", 127 }); 128 break; 129 } 130 case "openExternal": { 131 vscode.postMessage({ 132 type: "openExternal", 133 url: message.url, 134 }); 135 break; 136 } 137 case "ready": { 138 if (isReady) break; // Already handled, ignore duplicate ready messages 139 console.log("🫐 ✅ Received ready message, clearing timeout"); 140 clearAllTimeouts(); 141 isReady = true; 142 break; 143 } 144 default: { 145 // If debugging is enabled, surface unknown message types. 146 if (DEBUG && message?.type) { 147 console.log("🫐 Unhandled message type:", message.type); 148 } 149 } 150 } 151 }); 152 153 // Add event listener for when the window is focused. 154 // Only send on actual blur→focus transitions to prevent spurious re-focus 155 // events from clearing prompt text and reopening the keyboard curtain. 156 let parentHadFocus = document.hasFocus(); 157 window.addEventListener("blur", () => { 158 parentHadFocus = false; 159 }); 160 window.addEventListener("focus", () => { 161 if (!parentHadFocus) { 162 parentHadFocus = true; 163 iframe.contentWindow.postMessage({ type: "aesthetic-parent:focused" }, "*"); 164 } 165 }); 166 167 // ♻️ Start the refresh timeout cycle 168 readyTimeout = setTimeout(refresh, 5000); 169 170 function refresh() { 171 if (isReady) { 172 // Already ready, don't refresh 173 return; 174 } 175 refreshCount++; 176 if (refreshCount > maxRefreshAttempts) { 177 console.log("🫐 Max refresh attempts reached, assuming ready"); 178 clearTimeout(readyTimeout); 179 readyTimeout = null; 180 isReady = true; // Prevent further refreshes 181 return; 182 } 183 console.log(`🫐 Awaiting... (attempt ${refreshCount}/${maxRefreshAttempts})`); 184 185 // Post a message to the iframe to show retry status in boot log 186 try { 187 iframe.contentWindow?.postMessage({ 188 type: "boot-retry", 189 attempt: refreshCount, 190 maxAttempts: maxRefreshAttempts 191 }, "*"); 192 } catch (e) { 193 // Iframe might not be ready yet 194 } 195 196 const url = new URL(iframe.src); 197 url.searchParams.set("ac-timestamp", new Date().getTime()); 198 iframe.src = url.href; 199 readyTimeout = setTimeout(refresh, 5000); 200 } 201 202 // Also listen for iframe load event as a fallback 203 iframe.addEventListener('load', () => { 204 if (isReady) return; // Already handled 205 console.log("🫐 Iframe loaded"); 206 // Give the iframe content a bit of time to initialize and send ready message 207 loadTimeout = setTimeout(() => { 208 if (!isReady && readyTimeout) { 209 console.log("🫐 Clearing timeout after iframe load (fallback)"); 210 clearAllTimeouts(); 211 isReady = true; 212 } 213 }, 2000); 214 }); 215})();