branch:
client.tsx
11862 bytesRaw
import { useAgent } from "agents/react";
import { useRef, useState, Suspense, useCallback } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";

interface Message {
  id: string;
  text: string;
  timestamp: Date;
  type: "incoming" | "outgoing";
}

// Mock authentication service
async function getAuthToken(): Promise<string> {
  // Simulate API call delay
  await new Promise((resolve) => setTimeout(resolve, 500));
  return "demo-token-123";
}

async function getCurrentUser(): Promise<{ id: string; name: string }> {
  // Simulate user data fetch
  await new Promise((resolve) => setTimeout(resolve, 300));
  return { id: "demo-user", name: "Demo User" };
}

function AsyncAuthApp() {
  const [messages, setMessages] = useState<Message[]>([]);
  const inputRef = useRef<HTMLInputElement>(null);

  // Async authentication query
  const asyncQuery = useCallback(async () => {
    console.log("🔐 Fetching authentication data...");
    const [token, user] = await Promise.all([getAuthToken(), getCurrentUser()]);

    console.log("✅ Auth data fetched:", { token, userId: user.id });
    return {
      token,
      userId: user.id,
      timestamp: Date.now().toString() // Convert to string for WebSocket compatibility
    };
  }, []);

  // Cross-domain WebSocket connection with async authentication using unified useAgent
  const agent = useAgent({
    agent: "my-agent",
    host: "http://localhost:8787",
    query: asyncQuery, // Async function - automatically detected and cached
    onMessage: (message: MessageEvent) => {
      const newMessage: Message = {
        id: Math.random().toString(36).substring(7),
        text: message.data as string,
        timestamp: new Date(),
        type: "incoming"
      };
      setMessages((prev) => [...prev, newMessage]);
    },
    onError: (error: Event) => {
      console.error("WebSocket error:", error);
    }
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!inputRef.current || !inputRef.current.value.trim()) return;

    const text = inputRef.current.value;
    const newMessage: Message = {
      id: Math.random().toString(36).substring(7),
      text,
      timestamp: new Date(),
      type: "outgoing"
    };

    agent.send(text);
    setMessages((prev) => [...prev, newMessage]);
    inputRef.current.value = "";
  };

  const handleFetchRequest = async () => {
    try {
      // Get fresh auth token for HTTP request
      const token = await getAuthToken();

      const response = await fetch(
        "http://localhost:8787/agents/my-agent/default",
        {
          method: "GET",
          headers: {
            Authorization: `Bearer ${token}`,
            "X-API-Key": "demo-api-key"
          }
        }
      );
      const data = await response.text();
      const newMessage: Message = {
        id: Math.random().toString(36).substring(7),
        text: `HTTP Response: ${data}`,
        timestamp: new Date(),
        type: "incoming"
      };
      setMessages((prev) => [...prev, newMessage]);
    } catch (error) {
      console.error("Error fetching from server:", error);
      const errorMessage: Message = {
        id: Math.random().toString(36).substring(7),
        text: `HTTP Error: ${error}`,
        timestamp: new Date(),
        type: "incoming"
      };
      setMessages((prev) => [...prev, errorMessage]);
    }
  };

  const formatTime = (date: Date) => {
    return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
  };

  return (
    <div className="chat-container">
      <div className="auth-section">
        <h2>Cross-Domain Authentication Demo (Async)</h2>
        <div className="auth-info">
          <p>
            <strong>🚀 Async Authentication:</strong>
          </p>
          <p>• Token fetched dynamically from auth service</p>
          <p>• User data retrieved from API</p>
          <p>• WebSocket connection waits for auth completion</p>
          <p>• Uses React Suspense for loading states</p>
          <p>
            <strong>🌐 Cross-Domain Setup:</strong>
          </p>
          <p>• Client: {window.location.origin} (this page)</p>
          <p>• Server: http://localhost:8787 (different port)</p>
        </div>
      </div>

      <form className="message-form" onSubmit={handleSubmit}>
        <input
          type="text"
          ref={inputRef}
          className="message-input"
          placeholder="Type your message..."
        />
        <button type="submit">Send WebSocket Message</button>
      </form>

      <button
        type="button"
        onClick={handleFetchRequest}
        className="http-button"
      >
        Send Authenticated HTTP Request
      </button>

      <div className="messages-section">
        <h3>Messages</h3>
        <div className="messages">
          {messages.map((message) => (
            <div key={message.id} className={`message ${message.type}-message`}>
              <div>{message.text}</div>
              <div className="timestamp">{formatTime(message.timestamp)}</div>
            </div>
          ))}
        </div>
      </div>

      <div className="debug-section">
        <h4>Debug Information</h4>
        <div className="debug-info">
          <p>
            <strong>Agent:</strong> {agent.agent}
          </p>
          <p>
            <strong>Room:</strong> {agent.name}
          </p>
        </div>
      </div>
    </div>
  );
}

function StaticAuthApp() {
  const [authToken, setAuthToken] = useState("demo-token-123");
  const [messages, setMessages] = useState<Message[]>([]);
  const inputRef = useRef<HTMLInputElement>(null);
  const tokenInputRef = useRef<HTMLInputElement>(null);

  // Cross-domain WebSocket connection with static query parameter authentication
  const agent = useAgent({
    agent: "my-agent",
    host: "http://localhost:8787",
    query: {
      token: authToken, // Authentication token (demo-token-123)
      userId: "demo-user" // User identifier for server validation
    },
    onMessage: (message) => {
      const newMessage: Message = {
        id: Math.random().toString(36).substring(7),
        text: message.data as string,
        timestamp: new Date(),
        type: "incoming"
      };
      setMessages((prev) => [...prev, newMessage]);
    },
    onError: (error) => {
      console.error("WebSocket auth error:", error);
    }
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!inputRef.current || !inputRef.current.value.trim()) return;

    const text = inputRef.current.value;
    const newMessage: Message = {
      id: Math.random().toString(36).substring(7),
      text,
      timestamp: new Date(),
      type: "outgoing"
    };

    agent.send(text);
    setMessages((prev) => [...prev, newMessage]);
    inputRef.current.value = "";
  };

  const handleFetchRequest = async () => {
    const newMessage: Message = {
      id: Math.random().toString(36).substring(7),
      text: "",
      timestamp: new Date(),
      type: "incoming"
    };
    try {
      // Cross-domain HTTP request with header-based authentication
      const response = await fetch(
        "http://localhost:8787/agents/my-agent/default",
        {
          method: "GET",
          headers: {
            Authorization: `Bearer ${authToken}` // Bearer token authentication
          }
        }
      );
      const data = await response.text();
      newMessage.text = `HTTP Response: ${data}`;
    } catch (error) {
      console.error("Error fetching from server:", error);
      newMessage.text = `HTTP Error: ${error}`;
    } finally {
      setMessages((prev) => [...prev, newMessage]);
    }
  };

  const updateAuthToken = () => {
    if (tokenInputRef.current?.value) {
      setAuthToken(tokenInputRef.current.value);
    }
  };

  const formatTime = (date: Date) => {
    return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
  };

  return (
    <div className="chat-container">
      <div className="auth-section">
        <h2>Cross-Domain Authentication Demo</h2>
        <div className="auth-controls">
          <input
            ref={tokenInputRef}
            type="text"
            placeholder="Enter auth token"
            defaultValue={authToken}
          />
        </div>
        <button type="button" onClick={updateAuthToken}>
          Update Token
        </button>
        <div className="auth-info">
          <p>
            <strong>🌐 Cross-Domain Setup:</strong>
          </p>
          <p>• Client: {window.location.origin} (this page)</p>
          <p>• Server: http://localhost:8787 (different port)</p>
          <p>
            <strong>🔗 WebSocket Auth:</strong> Query parameter (token=
            {authToken})
          </p>
          <p>
            <strong>📡 HTTP Auth:</strong> Bearer token + API key in headers
          </p>
          <p>
            <strong>🎯 Valid Token:</strong> "demo-token-123"
          </p>
          <p>
            <strong>🎯 Valid API Key:</strong> "demo-api-key"
          </p>
        </div>
      </div>

      <form className="message-form" onSubmit={handleSubmit}>
        <input
          type="text"
          ref={inputRef}
          className="message-input"
          placeholder="Type your message..."
        />
        <button type="submit">Send WebSocket Message</button>
      </form>

      <button
        type="button"
        onClick={handleFetchRequest}
        className="http-button"
        disabled={!authToken}
      >
        Send Authenticated HTTP Request
      </button>

      <div className="messages-section">
        <h3>Messages</h3>
        <div className="messages">
          {messages.map((message) => (
            <div key={message.id} className={`message ${message.type}-message`}>
              <div>{message.text}</div>
              <div className="timestamp">{formatTime(message.timestamp)}</div>
            </div>
          ))}
        </div>
      </div>

      <div className="debug-section">
        <h4>Debug Information</h4>
        <div className="debug-info">
          <p>
            <strong>Agent:</strong> {agent.agent}
          </p>
          <p>
            <strong>Room:</strong> {agent.name}
          </p>
          <p>
            <strong>Auth Token:</strong> {authToken}
          </p>
        </div>
      </div>
    </div>
  );
}

function App() {
  const [useAsync, setUseAsync] = useState(false);

  return (
    <div>
      <div
        style={{
          padding: "20px",
          borderBottom: "2px solid #ddd",
          backgroundColor: "#f5f5f5"
        }}
      >
        <h1>Cross-Domain Authentication Examples</h1>
        <div style={{ marginBottom: "10px" }}>
          <label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
            <input
              type="checkbox"
              checked={useAsync}
              onChange={(e) => setUseAsync(e.target.checked)}
            />
            Use Async Authentication (useAgent with async query)
          </label>
        </div>
        <p style={{ margin: 0, fontSize: "14px", color: "#666" }}>
          Toggle between static authentication (useAgent) and async
          authentication (useAgent with async query)
        </p>
      </div>

      <Suspense
        fallback={
          <div style={{ padding: "20px", textAlign: "center" }}>
            <div>🔐 Loading authentication...</div>
            <div style={{ fontSize: "14px", color: "#666", marginTop: "8px" }}>
              Fetching auth token and user data...
            </div>
          </div>
        }
      >
        {useAsync ? <AsyncAuthApp /> : <StaticAuthApp />}
      </Suspense>
    </div>
  );
}

const root = createRoot(document.getElementById("root")!);
root.render(<App />);