Files
TurboTrades/test-client.html
2026-01-10 04:57:43 +00:00

1155 lines
34 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TurboTrades WebSocket Test Client</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 32px;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
}
.content {
padding: 30px;
}
.section {
margin-bottom: 30px;
}
.section-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #667eea;
padding-bottom: 8px;
}
.status {
display: inline-block;
padding: 8px 16px;
border-radius: 20px;
font-weight: 600;
font-size: 14px;
margin-bottom: 15px;
}
.status.connected {
background: #10b981;
color: white;
}
.status.disconnected {
background: #ef4444;
color: white;
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.input-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group label {
font-size: 14px;
font-weight: 500;
color: #555;
}
input,
button {
padding: 12px 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
font-family: inherit;
transition: all 0.2s;
}
input:focus {
outline: none;
border-color: #667eea;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
cursor: pointer;
font-weight: 600;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.messages {
background: #f9fafb;
border: 2px solid #e5e7eb;
border-radius: 8px;
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.message {
background: white;
border-left: 4px solid #667eea;
padding: 12px;
margin-bottom: 12px;
border-radius: 4px;
font-family: "Courier New", monospace;
font-size: 13px;
}
.message.received {
border-left-color: #10b981;
}
.message.sent {
border-left-color: #3b82f6;
}
.message.error {
border-left-color: #ef4444;
background: #fee;
}
.message-time {
color: #888;
font-size: 11px;
margin-bottom: 4px;
}
.message-type {
color: #667eea;
font-weight: 600;
margin-bottom: 4px;
}
.message-content {
color: #333;
white-space: pre-wrap;
word-break: break-word;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-top: 20px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: 700;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
}
.api-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.api-link {
display: inline-block;
padding: 8px 16px;
background: #f3f4f6;
border-radius: 6px;
text-decoration: none;
color: #667eea;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.api-link:hover {
background: #667eea;
color: white;
transform: translateY(-2px);
}
.clear-btn {
background: #ef4444;
padding: 8px 16px;
font-size: 14px;
}
@media (max-width: 768px) {
.controls {
grid-template-columns: 1fr;
}
.stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 TurboTrades WebSocket Test Client</h1>
<p>Test your WebSocket connection and API endpoints</p>
</div>
<div class="content">
<!-- Authentication Status Section -->
<div class="section">
<h2 class="section-title">🔐 Authentication Status</h2>
<div
id="authStatus"
style="
padding: 15px;
background: #fef3c7;
border-left: 4px solid #f59e0b;
border-radius: 6px;
margin-bottom: 15px;
"
>
<strong>⚠️ Not Authenticated</strong>
<p style="margin: 5px 0; font-size: 14px">
Login required for marketplace features (create/update listings,
set trade URL)
</p>
<button
onclick="window.open('http://localhost:3000/auth/steam', '_blank')"
style="margin-top: 10px"
>
🔐 Login with Steam
</button>
<button
onclick="checkAuthStatus()"
style="margin-top: 10px; background: #10b981"
>
🔄 Check Auth Status
</button>
</div>
<div
style="
margin-top: 10px;
padding: 10px;
background: #e0f2fe;
border-radius: 6px;
font-size: 13px;
"
>
<strong>💡 Tip:</strong> After logging in via Steam, paste your
access token in the "Access Token" field above, then click "Check
Auth Status" to verify.
</div>
</div>
<!-- Connection Section -->
<div class="section">
<h2 class="section-title">📡 Connection</h2>
<div class="status" id="status">Disconnected</div>
<div class="controls">
<div class="input-group">
<label for="wsUrl">WebSocket URL</label>
<input
type="text"
id="wsUrl"
value="ws://localhost:3000/ws"
placeholder="ws://localhost:3000/ws"
/>
</div>
<div class="input-group">
<label for="token">Access Token (optional)</label>
<input
type="text"
id="token"
placeholder="Leave empty for anonymous connection"
/>
</div>
</div>
<div class="controls">
<button id="connectBtn" onclick="connect()">Connect</button>
<button id="disconnectBtn" onclick="disconnect()" disabled>
Disconnect
</button>
<button onclick="sendPing()">Send Ping</button>
</div>
</div>
<!-- Statistics Section -->
<div class="section">
<h2 class="section-title">📊 Statistics</h2>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="msgReceived">0</div>
<div class="stat-label">Messages Received</div>
</div>
<div class="stat-card">
<div class="stat-value" id="msgSent">0</div>
<div class="stat-label">Messages Sent</div>
</div>
<div class="stat-card">
<div class="stat-value" id="uptime">0s</div>
<div class="stat-label">Connection Time</div>
</div>
</div>
</div>
<!-- Custom Message Section -->
<div class="section">
<h2 class="section-title">✉️ Send Custom Message</h2>
<div class="input-group">
<label for="customMessage">JSON Message</label>
<input
type="text"
id="customMessage"
placeholder='{"type": "ping"}'
value='{"type": "ping"}'
/>
</div>
<button onclick="sendCustomMessage()" style="margin-top: 10px">
Send Message
</button>
</div>
<!-- Socket Stress Tests Section -->
<div class="section">
<h2 class="section-title">🔥 Socket Stress Tests</h2>
<div class="controls">
<div class="input-group">
<label for="messageCount">Number of Messages</label>
<input
type="number"
id="messageCount"
value="10"
min="1"
max="1000"
/>
</div>
<div class="input-group">
<label for="messageInterval">Interval (ms)</label>
<input
type="number"
id="messageInterval"
value="100"
min="10"
max="5000"
/>
</div>
</div>
<div class="controls">
<button onclick="runStressTest()">🚀 Run Stress Test</button>
<button onclick="stopStressTest()">⏹️ Stop Test</button>
<button onclick="sendBurst()">💥 Send Burst (100 msgs)</button>
</div>
<div
style="
margin-top: 10px;
padding: 10px;
background: #f3f4f6;
border-radius: 6px;
"
>
<div style="font-size: 14px; color: #555">
<strong>Test Status:</strong> <span id="testStatus">Idle</span
><br />
<strong>Messages Queued:</strong> <span id="testProgress">0</span>
</div>
</div>
</div>
<!-- Trade/Marketplace Tests Section -->
<div class="section">
<h2 class="section-title">🛒 Trade & Marketplace Tests</h2>
<!-- Get Listings -->
<div style="margin-bottom: 20px">
<h3 style="font-size: 16px; margin-bottom: 10px; color: #667eea">
📋 Get Listings
</h3>
<div class="controls">
<div class="input-group">
<label for="gameFilter">Game</label>
<select
id="gameFilter"
style="
padding: 12px 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
"
>
<option value="">All</option>
<option value="cs2">CS2</option>
<option value="rust">Rust</option>
</select>
</div>
<div class="input-group">
<label for="minPrice">Min Price</label>
<input
type="number"
id="minPrice"
placeholder="0.00"
step="0.01"
/>
</div>
<div class="input-group">
<label for="maxPrice">Max Price</label>
<input
type="number"
id="maxPrice"
placeholder="1000.00"
step="0.01"
/>
</div>
</div>
<button onclick="getListings()" style="margin-top: 10px">
Get Listings
</button>
</div>
<!-- Create Listing -->
<div style="margin-bottom: 20px">
<h3 style="font-size: 16px; margin-bottom: 10px; color: #667eea">
Create Listing
</h3>
<div class="controls">
<div class="input-group">
<label for="itemName">Item Name</label>
<input
type="text"
id="itemName"
placeholder="AK-47 | Redline"
value="AK-47 | Redline"
/>
</div>
<div class="input-group">
<label for="itemGame">Game</label>
<select
id="itemGame"
style="
padding: 12px 16px;
border: 2px solid #e5e7eb;
border-radius: 8px;
"
>
<option value="cs2">CS2</option>
<option value="rust">Rust</option>
</select>
</div>
<div class="input-group">
<label for="itemPrice">Price</label>
<input
type="number"
id="itemPrice"
placeholder="45.99"
value="45.99"
step="0.01"
/>
</div>
</div>
<div class="input-group" style="margin-top: 10px">
<label for="itemDescription">Description (optional)</label>
<input
type="text"
id="itemDescription"
placeholder="Minimal wear, great stickers"
/>
</div>
<button onclick="createListing()" style="margin-top: 10px">
Create Listing (Requires Auth)
</button>
</div>
<!-- Update Price -->
<div style="margin-bottom: 20px">
<h3 style="font-size: 16px; margin-bottom: 10px; color: #667eea">
💰 Update Listing Price
</h3>
<div class="controls">
<div class="input-group">
<label for="listingId">Listing ID</label>
<input type="text" id="listingId" placeholder="listing_123" />
</div>
<div class="input-group">
<label for="newPrice">New Price</label>
<input
type="number"
id="newPrice"
placeholder="39.99"
step="0.01"
/>
</div>
</div>
<button onclick="updateListingPrice()" style="margin-top: 10px">
Update Price (Requires Auth)
</button>
</div>
<!-- Trade URL -->
<div>
<h3 style="font-size: 16px; margin-bottom: 10px; color: #667eea">
🔗 Set Trade URL
</h3>
<div class="input-group">
<label for="tradeUrl">Steam Trade URL</label>
<input
type="text"
id="tradeUrl"
placeholder="https://steamcommunity.com/tradeoffer/new/?partner=..."
/>
</div>
<button onclick="setTradeUrl()" style="margin-top: 10px">
Set Trade URL (Requires Auth)
</button>
</div>
</div>
<!-- Messages Section -->
<div class="section">
<h2 class="section-title">💬 Messages</h2>
<button class="clear-btn" onclick="clearMessages()">
Clear Messages
</button>
<div class="messages" id="messages">
<p style="color: #888; text-align: center">
No messages yet. Connect to start receiving messages.
</p>
</div>
</div>
<!-- API Endpoints Section -->
<div class="section">
<h2 class="section-title">🔗 Quick API Links</h2>
<div class="api-links">
<a
href="http://localhost:3000/health"
target="_blank"
class="api-link"
>Health Check</a
>
<a href="http://localhost:3000/" target="_blank" class="api-link"
>API Info</a
>
<a
href="http://localhost:3000/auth/steam"
target="_blank"
class="api-link"
>Steam Login</a
>
<a
href="http://localhost:3000/auth/me"
target="_blank"
class="api-link"
>Get Current User</a
>
<a
href="http://localhost:3000/marketplace/listings"
target="_blank"
class="api-link"
>View Listings</a
>
</div>
</div>
</div>
</div>
<script>
let ws = null;
let messageCount = { received: 0, sent: 0 };
let connectTime = null;
let uptimeInterval = null;
let stressTestInterval = null;
let stressTestCount = 0;
function updateStatus(connected) {
const statusEl = document.getElementById("status");
const connectBtn = document.getElementById("connectBtn");
const disconnectBtn = document.getElementById("disconnectBtn");
if (connected) {
statusEl.textContent = "✅ Connected";
statusEl.className = "status connected";
connectBtn.disabled = true;
disconnectBtn.disabled = false;
connectTime = Date.now();
startUptimeCounter();
} else {
statusEl.textContent = "❌ Disconnected";
statusEl.className = "status disconnected";
connectBtn.disabled = false;
disconnectBtn.disabled = true;
stopUptimeCounter();
}
}
function startUptimeCounter() {
uptimeInterval = setInterval(() => {
if (connectTime) {
const seconds = Math.floor((Date.now() - connectTime) / 1000);
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
document.getElementById("uptime").textContent =
mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
}
}, 1000);
}
function stopUptimeCounter() {
if (uptimeInterval) {
clearInterval(uptimeInterval);
uptimeInterval = null;
}
document.getElementById("uptime").textContent = "0s";
connectTime = null;
}
function addMessage(type, direction, content) {
const messagesEl = document.getElementById("messages");
const messageEl = document.createElement("div");
messageEl.className = `message ${direction}`;
const now = new Date();
const time = now.toLocaleTimeString();
messageEl.innerHTML = `
<div class="message-time">${time}</div>
<div class="message-type">${type}</div>
<div class="message-content">${content}</div>
`;
if (messagesEl.querySelector("p")) {
messagesEl.innerHTML = "";
}
messagesEl.insertBefore(messageEl, messagesEl.firstChild);
// Update stats
if (direction === "received") {
messageCount.received++;
document.getElementById("msgReceived").textContent =
messageCount.received;
} else if (direction === "sent") {
messageCount.sent++;
document.getElementById("msgSent").textContent = messageCount.sent;
}
}
function connect() {
const url = document.getElementById("wsUrl").value;
const token = document.getElementById("token").value;
const wsUrl = token ? `${url}?token=${token}` : url;
try {
ws = new WebSocket(wsUrl);
ws.onopen = (event) => {
console.log("WebSocket connected");
updateStatus(true);
addMessage(
"CONNECTION",
"received",
"Connected to WebSocket server"
);
};
ws.onmessage = (event) => {
console.log("Message received:", event.data);
try {
const data = JSON.parse(event.data);
addMessage(
data.type || "MESSAGE",
"received",
JSON.stringify(data, null, 2)
);
} catch (e) {
addMessage("MESSAGE", "received", event.data);
}
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
addMessage("ERROR", "error", "WebSocket error occurred");
};
ws.onclose = (event) => {
console.log("WebSocket closed:", event.code, event.reason);
updateStatus(false);
addMessage(
"CONNECTION",
"error",
`Disconnected (Code: ${event.code})`
);
};
} catch (error) {
console.error("Failed to connect:", error);
addMessage("ERROR", "error", `Failed to connect: ${error.message}`);
}
}
function disconnect() {
if (ws) {
ws.close(1000, "Client disconnect");
ws = null;
}
}
function sendPing() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
alert("WebSocket is not connected");
return;
}
const message = { type: "ping" };
ws.send(JSON.stringify(message));
addMessage("PING", "sent", JSON.stringify(message, null, 2));
}
function sendCustomMessage() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
alert("WebSocket is not connected");
return;
}
const messageInput = document.getElementById("customMessage").value;
try {
const message = JSON.parse(messageInput);
ws.send(JSON.stringify(message));
addMessage("CUSTOM", "sent", JSON.stringify(message, null, 2));
} catch (e) {
alert("Invalid JSON format");
}
}
function clearMessages() {
const messagesEl = document.getElementById("messages");
messagesEl.innerHTML =
'<p style="color: #888; text-align: center;">Messages cleared.</p>';
messageCount = { received: 0, sent: 0 };
document.getElementById("msgReceived").textContent = "0";
document.getElementById("msgSent").textContent = "0";
}
// Socket Stress Tests
function runStressTest() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
alert("WebSocket is not connected");
return;
}
const count = parseInt(document.getElementById("messageCount").value);
const interval = parseInt(
document.getElementById("messageInterval").value
);
stressTestCount = 0;
document.getElementById("testStatus").textContent = "Running...";
document.getElementById("testProgress").textContent = `0 / ${count}`;
stressTestInterval = setInterval(() => {
if (stressTestCount >= count) {
stopStressTest();
return;
}
const message = {
type: "stress_test",
data: { index: stressTestCount, timestamp: Date.now() },
};
ws.send(JSON.stringify(message));
stressTestCount++;
document.getElementById(
"testProgress"
).textContent = `${stressTestCount} / ${count}`;
}, interval);
}
function stopStressTest() {
if (stressTestInterval) {
clearInterval(stressTestInterval);
stressTestInterval = null;
}
document.getElementById("testStatus").textContent = "Stopped";
}
function sendBurst() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
alert("WebSocket is not connected");
return;
}
document.getElementById("testStatus").textContent = "Sending burst...";
for (let i = 0; i < 100; i++) {
const message = {
type: "burst_test",
data: { index: i, timestamp: Date.now() },
};
ws.send(JSON.stringify(message));
}
document.getElementById("testStatus").textContent =
"Burst sent (100 messages)";
document.getElementById("testProgress").textContent = "100";
}
// Helper function to get auth headers
function getAuthHeaders() {
const token = document.getElementById("token").value;
const headers = {
"Content-Type": "application/json",
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
return headers;
}
// Check authentication status
async function checkAuthStatus() {
try {
const token = document.getElementById("token").value;
const headers = {};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch("http://localhost:3000/auth/me", {
credentials: "include",
headers: headers,
});
const authStatusDiv = document.getElementById("authStatus");
if (response.ok) {
const data = await response.json();
authStatusDiv.innerHTML = `
<strong>✅ Authenticated</strong>
<p style="margin: 5px 0; font-size: 14px">
Logged in as: <strong>${
data.user.username
}</strong> (Steam ID: ${data.user.steamId})
</p>
<p style="margin: 5px 0; font-size: 14px">
Trade URL: ${data.user.tradeUrl ? "✅ Set" : "❌ Not set"}
</p>
<button onclick="checkAuthStatus()" style="margin-top: 10px; background: #10b981">
🔄 Refresh Status
</button>
`;
authStatusDiv.style.background = "#d1fae5";
authStatusDiv.style.borderLeftColor = "#10b981";
addMessage(
"AUTH",
"received",
`Authenticated as ${data.user.username}\nSteam ID: ${data.user.steamId}`
);
} else {
authStatusDiv.innerHTML = `
<strong>⚠️ Not Authenticated</strong>
<p style="margin: 5px 0; font-size: 14px">
Login required for marketplace features
</p>
<button onclick="window.open('http://localhost:3000/auth/steam', '_blank')" style="margin-top: 10px">
🔐 Login with Steam
</button>
<button onclick="checkAuthStatus()" style="margin-top: 10px; background: #10b981">
🔄 Check Auth Status
</button>
`;
authStatusDiv.style.background = "#fef3c7";
authStatusDiv.style.borderLeftColor = "#f59e0b";
addMessage("AUTH", "error", "Not authenticated");
}
} catch (error) {
addMessage("ERROR", "error", `Auth check failed: ${error.message}`);
}
}
// Trade/Marketplace API Tests
async function getListings() {
try {
const game = document.getElementById("gameFilter").value;
const minPrice = document.getElementById("minPrice").value;
const maxPrice = document.getElementById("maxPrice").value;
const url = "http://localhost:3000/marketplace/listings?";
if (game) url += `game=${game}&`;
if (minPrice) url += `minPrice=${minPrice}&`;
if (maxPrice) url += `maxPrice=${maxPrice}&`;
const response = await fetch(url, {
credentials: "include",
headers: getAuthHeaders(),
});
const data = await response.json();
addMessage(
"API",
"received",
`GET /marketplace/listings\nStatus: ${
response.status
}\n${JSON.stringify(data, null, 2)}`
);
if (response.ok) {
alert(`Found ${data.listings?.length || 0} listings`);
} else if (response.status === 401) {
alert(
"⚠️ Not authenticated. This endpoint is public but login at:\nhttp://localhost:3000/auth/steam"
);
}
} catch (error) {
addMessage(
"ERROR",
"error",
`Failed to get listings: ${error.message}`
);
}
}
async function createListing() {
try {
const itemName = document.getElementById("itemName").value;
const game = document.getElementById("itemGame").value;
const price = parseFloat(document.getElementById("itemPrice").value);
const description = document.getElementById("itemDescription").value;
if (!itemName || !price) {
alert("Item name and price are required");
return;
}
const response = await fetch(
"http://localhost:3000/marketplace/listings",
{
method: "POST",
headers: getAuthHeaders(),
credentials: "include",
body: JSON.stringify({
itemName,
game,
price,
description: description || undefined,
}),
}
);
const data = await response.json();
addMessage(
"API",
response.ok ? "sent" : "error",
`POST /marketplace/listings\nStatus: ${
response.status
}\n${JSON.stringify(data, null, 2)}`
);
if (response.ok) {
alert(`✅ Listing created! ID: ${data.listing?.id}`);
} else if (response.status === 401) {
alert(
"🔐 Authentication Required!\n\n1. Login at: http://localhost:3000/auth/steam\n2. Complete Steam OAuth\n3. Try again"
);
window.open("http://localhost:3000/auth/steam", "_blank");
} else if (response.status === 400) {
alert(
`${
data.message || "Validation error"
}\n\nMake sure you have:\n- Set your trade URL\n- Verified your email (if required)`
);
}
} catch (error) {
addMessage(
"ERROR",
"error",
`Failed to create listing: ${error.message}`
);
}
}
async function updateListingPrice() {
try {
const listingId = document.getElementById("listingId").value;
const newPrice = parseFloat(
document.getElementById("newPrice").value
);
if (!listingId || !newPrice) {
alert("Listing ID and new price are required");
return;
}
const response = await fetch(
`http://localhost:3000/marketplace/listings/${listingId}/price`,
{
method: "PATCH",
headers: getAuthHeaders(),
credentials: "include",
body: JSON.stringify({ price: newPrice }),
}
);
const data = await response.json();
addMessage(
"API",
response.ok ? "sent" : "error",
`PATCH /marketplace/listings/${listingId}/price\nStatus: ${
response.status
}\n${JSON.stringify(data, null, 2)}`
);
if (response.ok) {
alert("✅ Price updated successfully!");
} else if (response.status === 401) {
alert(
"🔐 Authentication Required!\n\n1. Login at: http://localhost:3000/auth/steam\n2. Complete Steam OAuth\n3. Try again"
);
window.open("http://localhost:3000/auth/steam", "_blank");
} else if (response.status === 403) {
alert("❌ Forbidden: You can only update your own listings");
}
} catch (error) {
addMessage(
"ERROR",
"error",
`Failed to update price: ${error.message}`
);
}
}
async function setTradeUrl() {
try {
const tradeUrl = document.getElementById("tradeUrl").value;
if (!tradeUrl) {
alert("Trade URL is required");
return;
}
const response = await fetch("http://localhost:3000/user/trade-url", {
method: "PUT",
headers: getAuthHeaders(),
credentials: "include",
body: JSON.stringify({ tradeUrl }),
});
const data = await response.json();
addMessage(
"API",
response.ok ? "sent" : "error",
`PUT /user/trade-url\nStatus: ${response.status}\n${JSON.stringify(
data,
null,
2
)}`
);
if (response.ok) {
alert("✅ Trade URL set successfully!");
} else if (response.status === 401) {
alert(
"🔐 Authentication Required!\n\n1. Login at: http://localhost:3000/auth/steam\n2. Complete Steam OAuth\n3. Try again"
);
window.open("http://localhost:3000/auth/steam", "_blank");
} else if (response.status === 400) {
alert(
"❌ Invalid Trade URL format!\n\nMust be:\nhttps://steamcommunity.com/tradeoffer/new/?partner=XXXXX&token=XXXXX\n\nGet yours from:\nSteam > Inventory > Trade Offers > Who can send me trade offers?"
);
}
} catch (error) {
addMessage(
"ERROR",
"error",
`Failed to set trade URL: ${error.message}`
);
}
}
// Initialize on page load
updateStatus(false);
checkAuthStatus();
// Auto-reconnect on page reload if previously connected
window.addEventListener("beforeunload", () => {
if (ws) {
ws.close();
}
if (stressTestInterval) {
clearInterval(stressTestInterval);
}
});
</script>
</body>
</html>