Spaces:
Running
Running
| /** | |
| * Iframe Dialog Test Logic | |
| * Tests WebSerial + WebUSB dialog behavior in iframe vs standalone environments | |
| */ | |
| import { findPort } from "@lerobot/web"; | |
| let isRunning = false; | |
| function log(message: string) { | |
| const logElement = document.getElementById("log"); | |
| if (logElement) { | |
| const timestamp = new Date().toLocaleTimeString(); | |
| logElement.textContent += `[${timestamp}] ${message}\n`; | |
| logElement.scrollTop = logElement.scrollHeight; | |
| } | |
| } | |
| function setButtonState(buttonId: string, running: boolean) { | |
| const button = document.getElementById(buttonId) as HTMLButtonElement; | |
| if (button) { | |
| button.disabled = running; | |
| if (running) { | |
| button.textContent = "β³ Testing..."; | |
| } else { | |
| // Restore original text | |
| if (buttonId === "testStandalone") { | |
| button.textContent = "π₯οΈ Test Standalone (Current Window)"; | |
| } | |
| } | |
| } | |
| } | |
| // Mock implementation to test dialog behavior without actual findPort | |
| async function testDialogBehavior(environment: "standalone" | "iframe") { | |
| log(`π― Testing dialog behavior in ${environment} environment...`); | |
| try { | |
| // Method 1: Sequential (current approach) | |
| log("π‘ Method 1: Sequential WebSerial β WebUSB"); | |
| log("πΈ Requesting WebSerial port..."); | |
| const serialPort = await navigator.serial.requestPort(); | |
| log("β WebSerial port selected"); | |
| log("πΈ Requesting WebUSB device..."); | |
| const usbDevice = await navigator.usb.requestDevice({ filters: [] }); | |
| log("β WebUSB device selected"); | |
| log("π Sequential method succeeded!"); | |
| return { serialPort, usbDevice, method: "sequential" }; | |
| } catch (error: any) { | |
| log(`β Sequential method failed: ${error.message}`); | |
| // Method 2: Simultaneous (new approach) | |
| try { | |
| log("π‘ Method 2: Simultaneous WebSerial + WebUSB"); | |
| log("π Starting both dialogs simultaneously..."); | |
| const [serialPortPromise, usbDevicePromise] = [ | |
| navigator.serial.requestPort(), | |
| navigator.usb.requestDevice({ filters: [] }), | |
| ]; | |
| log("β³ Waiting for both dialogs..."); | |
| const [serialPort, usbDevice] = await Promise.all([ | |
| serialPortPromise, | |
| usbDevicePromise, | |
| ]); | |
| log("β Both dialogs completed!"); | |
| log("π Simultaneous method succeeded!"); | |
| return { serialPort, usbDevice, method: "simultaneous" }; | |
| } catch (simultaneousError: any) { | |
| log(`β Simultaneous method also failed: ${simultaneousError.message}`); | |
| throw simultaneousError; | |
| } | |
| } | |
| } | |
| // Test using actual findPort function | |
| async function testActualFindPort() { | |
| log("π Testing actual findPort function..."); | |
| try { | |
| const findProcess = await findPort(); | |
| const robots = await findProcess.result; | |
| log(`β findPort succeeded! Found ${robots.length} robots`); | |
| robots.forEach((robot, index) => { | |
| log(` Robot ${index + 1}: ${robot.name} (${robot.robotType})`); | |
| }); | |
| return robots; | |
| } catch (error: any) { | |
| log(`β findPort failed: ${error.message}`); | |
| throw error; | |
| } | |
| } | |
| declare global { | |
| interface Window { | |
| clearLog: () => void; | |
| testStandalone: () => Promise<void>; | |
| loadIframe: (mode: string) => void; | |
| } | |
| } | |
| window.clearLog = function () { | |
| const logElement = document.getElementById("log"); | |
| if (logElement) { | |
| logElement.textContent = "Log cleared.\n"; | |
| } | |
| }; | |
| window.loadIframe = function (mode: string) { | |
| const iframe = document.getElementById("testFrame") as HTMLIFrameElement; | |
| const infoElement = document.getElementById("iframeInfo"); | |
| if (!iframe) return; | |
| // Clear existing attributes | |
| iframe.removeAttribute("sandbox"); | |
| iframe.removeAttribute("allow"); | |
| let iframeContent = ""; | |
| let description = ""; | |
| switch (mode) { | |
| case "permissive": | |
| // Most permissive - similar to our original test | |
| iframe.setAttribute("allow", "serial; usb"); | |
| description = "Permissive: Full access to WebSerial and WebUSB"; | |
| iframeContent = generateIframeContent(); | |
| break; | |
| case "restricted": | |
| // Restricted with sandbox - might block certain permissions | |
| iframe.setAttribute( | |
| "sandbox", | |
| "allow-scripts allow-same-origin allow-popups allow-forms" | |
| ); | |
| iframe.setAttribute("allow", "serial; usb"); | |
| description = "Restricted: Sandboxed with limited permissions"; | |
| iframeContent = generateIframeContent(); | |
| break; | |
| case "crossorigin": | |
| // Cross-origin simulation (limited local test) | |
| iframe.setAttribute("sandbox", "allow-scripts allow-popups allow-forms"); | |
| iframe.setAttribute("allow", "serial; usb"); | |
| description = "Cross-Origin: Different origin with sandbox restrictions"; | |
| iframeContent = generateIframeContent(); | |
| break; | |
| } | |
| if (infoElement) { | |
| infoElement.textContent = `Current iframe mode: ${description}`; | |
| } | |
| iframe.src = | |
| "data:text/html;charset=utf-8," + encodeURIComponent(iframeContent); | |
| log(`π Loaded iframe in ${mode} mode: ${description}`); | |
| }; | |
| window.testStandalone = async function () { | |
| if (isRunning) return; | |
| isRunning = true; | |
| setButtonState("testStandalone", true); | |
| log("π§ͺ Testing with actual findPort function..."); | |
| try { | |
| // Test the actual findPort function with our new fallback logic | |
| await testActualFindPort(); | |
| log("\nβ findPort test completed!"); | |
| } catch (error: any) { | |
| log(`β findPort test failed: ${error.message}`); | |
| // Analyze the error | |
| if (error.message.includes("user gesture")) { | |
| log("π User gesture consumption detected!"); | |
| log("π‘ This confirms the issue - WebSerial consumes the gesture"); | |
| } else if (error.message.includes("cancelled")) { | |
| log("π User cancelled dialog - this is expected for testing"); | |
| } else if (error.message.includes("No port selected")) { | |
| log("π Dialog conflict detected - this should trigger fallback mode"); | |
| } | |
| } finally { | |
| isRunning = false; | |
| setButtonState("testStandalone", false); | |
| } | |
| }; | |
| // Generate iframe content dynamically | |
| function generateIframeContent(): string { | |
| return ` | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Iframe Test Content</title> | |
| <style> | |
| body { | |
| font-family: system-ui, sans-serif; | |
| padding: 20px; | |
| margin: 0; | |
| background: white; | |
| text-align: center; | |
| } | |
| button { | |
| background: #2563eb; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| margin: 5px; | |
| font-size: 14px; | |
| } | |
| button:hover { | |
| background: #1d4ed8; | |
| } | |
| button:disabled { | |
| background: #9ca3af; | |
| cursor: not-allowed; | |
| } | |
| .status { | |
| margin: 10px 0; | |
| padding: 10px; | |
| background: #f3f4f6; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h3>πΌοΈ Inside Iframe</h3> | |
| <p>This simulates HuggingFace Spaces environment</p> | |
| <button onclick="testActualFindPort()">Test Actual findPort Function</button> | |
| <button onclick="testIframeDialogs()">Test Mock Sequential</button> | |
| <button onclick="testIframeSimultaneous()">Test Mock Simultaneous</button> | |
| <div id="iframeStatus" class="status">Ready to test...</div> | |
| <script type="module"> | |
| import { findPort } from '../packages/web/src/index.js'; | |
| function updateStatus(message) { | |
| const status = document.getElementById('iframeStatus'); | |
| const timestamp = new Date().toLocaleTimeString(); | |
| status.textContent = timestamp + ': ' + message; | |
| // Also log to parent window | |
| window.parent.postMessage({ | |
| type: 'iframe-log', | |
| message: message | |
| }, '*'); | |
| } | |
| window.testIframeDialogs = async function() { | |
| updateStatus('Testing sequential dialogs...'); | |
| try { | |
| updateStatus('Requesting WebSerial port...'); | |
| const serialPort = await navigator.serial.requestPort(); | |
| updateStatus('WebSerial port selected'); | |
| updateStatus('Requesting WebUSB device...'); | |
| const usbDevice = await navigator.usb.requestDevice({ filters: [] }); | |
| updateStatus('WebUSB device selected - SUCCESS!'); | |
| } catch (error) { | |
| updateStatus('Failed: ' + error.message); | |
| } | |
| }; | |
| window.testIframeSimultaneous = async function() { | |
| updateStatus('Testing simultaneous dialogs...'); | |
| try { | |
| updateStatus('Starting both dialogs simultaneously...'); | |
| const [serialPortPromise, usbDevicePromise] = [ | |
| navigator.serial.requestPort(), | |
| navigator.usb.requestDevice({ filters: [] }) | |
| ]; | |
| const [serialPort, usbDevice] = await Promise.all([ | |
| serialPortPromise, | |
| usbDevicePromise | |
| ]); | |
| updateStatus('Both dialogs completed - SUCCESS!'); | |
| } catch (error) { | |
| updateStatus('Failed: ' + error.message); | |
| } | |
| }; | |
| // Test actual findPort function (imported at top) | |
| window.testActualFindPort = async function() { | |
| updateStatus('Testing actual findPort function...'); | |
| try { | |
| updateStatus('Starting findPort with fallback behavior...'); | |
| const findProcess = await findPort({ | |
| onMessage: (msg) => updateStatus('findPort: ' + msg) | |
| }); | |
| const robots = await findProcess.result; | |
| updateStatus('SUCCESS! Found ' + robots.length + ' robots'); | |
| robots.forEach((robot, index) => { | |
| updateStatus('Robot ' + (index + 1) + ': ' + robot.name + ' (' + robot.robotType + ')'); | |
| }); | |
| } catch (error) { | |
| updateStatus('findPort failed: ' + error.message); | |
| // Analyze the error for debugging | |
| if (error.message.includes('No port selected')) { | |
| updateStatus('π This should trigger sequential fallback mode'); | |
| } else if (error.message.includes('cancelled')) { | |
| updateStatus('π User cancelled dialog - expected for testing'); | |
| } | |
| } | |
| }; | |
| // Check environment | |
| updateStatus('Environment: ' + (window === window.top ? 'Standalone' : 'Iframe')); | |
| updateStatus('WebSerial: ' + ('serial' in navigator ? 'Supported' : 'Not supported')); | |
| updateStatus('WebUSB: ' + ('usb' in navigator ? 'Supported' : 'Not supported')); | |
| </script> | |
| </body> | |
| </html> | |
| `; | |
| } | |
| // Initialize on DOM load | |
| document.addEventListener("DOMContentLoaded", () => { | |
| // Check browser support | |
| if (!("serial" in navigator)) { | |
| log("β WebSerial API not supported in this browser"); | |
| log("π‘ Try Chrome/Edge with --enable-web-serial flag"); | |
| const button = document.getElementById( | |
| "testStandalone" | |
| ) as HTMLButtonElement; | |
| if (button) button.disabled = true; | |
| } else { | |
| log("β WebSerial API supported"); | |
| } | |
| if (!("usb" in navigator)) { | |
| log("β WebUSB API not supported in this browser"); | |
| log("π‘ Try Chrome/Edge with --enable-web-usb flag"); | |
| } else { | |
| log("β WebUSB API supported"); | |
| } | |
| log("π― Environment: " + (window === window.top ? "Standalone" : "Iframe")); | |
| log("Ready to test dialog behavior..."); | |
| // Set up iframe content - start with permissive mode | |
| window.loadIframe("permissive"); | |
| // Listen for messages from iframe | |
| window.addEventListener("message", (event) => { | |
| if (event.data.type === "iframe-log") { | |
| log(`[IFRAME] ${event.data.message}`); | |
| } | |
| }); | |
| }); | |