Monorepo for Aesthetic.Computer
aesthetic.computer
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})();