1155 lines
34 KiB
HTML
1155 lines
34 KiB
HTML
<!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>
|