Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/components/desktop/DesktopLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { WalletPanel } from '../wallet/WalletPanel';
import { WalletRequiredBlocker } from '../agents/WalletRequiredBlocker';
import { ActivityTicker } from '../activity';
import { Footer } from '../layout/Footer';
import { normalizeUrl } from '../../utils/normalizeUrl';

const CUSTOM_URL_PRESETS = [
{ label: 'Sphere Connect Example', url: 'https://unicity-sphere.github.io/sphere-sdk-connect-example/' },
Expand Down Expand Up @@ -45,11 +46,9 @@ export function DesktopLayout() {

const handleCustomUrlSubmit = (e: React.FormEvent) => {
e.preventDefault();
let url = customUrlInput.trim();
if (!url) return;
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = url.includes('localhost') || url.match(/^\d/) ? `http://${url}` : `https://${url}`;
}
const input = customUrlInput.trim();
if (!input) return;
const url = normalizeUrl(input);
openTab('custom', { url, label: new URL(url).hostname });
navigate(`/agents/custom?url=${encodeURIComponent(url)}`);
setCustomUrlInput('');
Expand Down
10 changes: 7 additions & 3 deletions src/pages/AgentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useParams, useSearchParams, Navigate } from 'react-router-dom';
import { DesktopLayout } from '../components/desktop/DesktopLayout';
import { getAgentConfig } from '../config/activities';
import { useDesktopState } from '../hooks/useDesktopState';
import { normalizeUrl } from '../utils/normalizeUrl';

export function AgentPage() {
const { agentId } = useParams<{ agentId: string }>();
Expand All @@ -16,11 +17,14 @@ export function AgentPage() {
useEffect(() => {
if (!agentId) return;

// Custom agent with ?url= parameter — open iframe directly
// Custom agent with ?url= parameter — open iframe directly.
// Normalize the scheme first (a bare host like "boxy-run.fly.dev" would make
// `new URL()` throw, falling back to the prompt) so it matches the in-app
// prompt path, which already prepends a scheme via normalizeUrl.
if (agentId === 'custom' && customUrl) {
try {
const hostname = new URL(customUrl).hostname;
openTab('custom', { url: customUrl, label: hostname });
const url = normalizeUrl(customUrl);
openTab('custom', { url, label: new URL(url).hostname });
} catch {
// Invalid URL — fall through to open the custom URL prompt
openTab(agentId);
Expand Down
14 changes: 14 additions & 0 deletions src/utils/normalizeUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Prepend a scheme to a bare host so it can be parsed by `new URL()` and loaded
* in an iframe. localhost / bare IPs default to http, everything else to https.
*
* Used by BOTH the in-app custom-URL prompt (DesktopLayout) and the
* `/agents/custom?url=` deep-link handler (AgentPage) so a value like
* `boxy-run.fly.dev` resolves to `https://boxy-run.fly.dev` in both paths.
* A URL that already carries a scheme is returned unchanged (idempotent).
*/
export function normalizeUrl(input: string): string {
const url = input.trim();
if (url.startsWith('http://') || url.startsWith('https://')) return url;
return url.includes('localhost') || /^\d/.test(url) ? `http://${url}` : `https://${url}`;
}
9 changes: 7 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ export default defineConfig(({ mode }) => {
// both "/" and deep routes like "/home", "/agents/dm", etc.
server.middlewares.use((req, _res, next) => {
const url = req.url || '';
const isProxied = proxyPaths.some((p) => url.startsWith(p));
const isAsset = url.startsWith('/src/') || url.startsWith('/node_modules/') || url.startsWith('/@') || url.includes('.');
// Classify by the PATH ONLY — stripping the query first. Otherwise a
// route whose query value contains a dot (e.g.
// /agents/custom?url=boxy-run.fly.dev) is misread as a static asset
// by the `.` check below and never falls back to index.html (404).
const pathname = url.split('?')[0];
const isProxied = proxyPaths.some((p) => pathname.startsWith(p));
const isAsset = pathname.startsWith('/src/') || pathname.startsWith('/node_modules/') || pathname.startsWith('/@') || pathname.includes('.');
if (!isProxied && !isAsset) {
req.url = '/src/index.html';
}
Expand Down
Loading