This document outlines the API interface for the browser-compatible implementation of the UniQuake token integration. The API will enable the creation of web client and server pages that can connect to the same master server as our Node.js implementations, while maintaining all token functionality.
The browser implementation will consist of the following core components:
- UniQuakeTokenService - Browser-compatible token management service
- BrowserGameClient - Client implementation for browser environment
- BrowserGameServer - Server implementation for browser environment
- P2PConnection - WebRTC/WebSocket connection wrapper
- MasterServerConnector - Interface to the master server
- GameIntegration - Interface for actual game integration
- MockGame - Mock game implementation for testing
class UniQuakeTokenService {
/**
* Create a new token service instance
* @param {Object} config - Configuration options
* @param {string} [config.secret] - Secret key (generated if not provided)
* @param {string} [config.username] - Username (generated if not provided)
* @param {string} [config.gateway] - Unicity gateway URL (uses default if not provided)
*/
constructor(config = {});
/**
* Initialize the token service asynchronously
* @returns {Promise<boolean>} - Whether initialization succeeded
*/
async init();
/**
* Get identity information
* @returns {Object} - Contains username and pubkey
*/
getIdentity();
/**
* Mint new tokens
* @param {number} [count=1] - Number of tokens to mint
* @param {string} [value='1'] - Value for each token
* @returns {Promise<Array>} - Array of minted tokens
*/
async mintCoins(count, value);
/**
* Send an entry token to join a game
* @param {string} recipientPubkey - Recipient's public key
* @returns {Promise<Object>} - Token flow for transmission
*/
async sendEntryToken(recipientPubkey);
/**
* Process a received token
* @param {Object|string} tokenFlow - Token flow to process
* @returns {Promise<Object>} - Result of token processing
*/
async receiveToken(tokenFlow);
/**
* Get list of spendable tokens
* @param {string} tokenClassId - Token class ID to filter by
* @returns {Promise<Array>} - Array of spendable tokens
*/
async getSpendableTokens(tokenClassId);
/**
* Create a game state token
* @param {Object} gameState - Game state to tokenize
* @returns {Promise<Object>} - Created state token
*/
async createGameStateToken(gameState);
/**
* Update an existing game state token
* @param {Object} stateToken - Current state token
* @param {Object} newState - New game state
* @returns {Promise<Object>} - Updated token
*/
async updateGameStateToken(stateToken, newState);
/**
* Verify a game state token
* @param {Object} tokenFlow - Token flow to verify
* @returns {Promise<Object>} - Verification result
*/
async verifyGameStateToken(tokenFlow);
/**
* Verify if local game state matches the verified state
* @param {Object} localState - Local game state to verify
* @returns {boolean} - True if state matches
*/
verifyLocalGameState(localState);
/**
* Get token status and inventory information
* @returns {Object} - Status of token pool and game state
*/
getTokenStatus();
/**
* Send tokens to a recipient (for reward distribution)
* @param {Array} tokens - Array of tokens to send
* @param {string} recipientPubkey - Recipient's public key
* @returns {Promise<Array>} - Array of token flows
*/
async sendTokensToRecipient(tokens, recipientPubkey);
}class BrowserGameClient {
/**
* Create a new browser game client
* @param {Object} config - Client configuration
* @param {string} [config.masterServer='localhost:27950'] - Master server address
* @param {string} [config.playerName='Player'] - Player name
* @param {boolean} [config.autoConnect=false] - Whether to auto-connect to a server
* @param {string} [config.autoConnectId=null] - Server ID to auto-connect to
* @param {number} [config.mintTokens=0] - Number of tokens to mint at startup
* @param {Function} [config.onStatusUpdate] - Callback for status updates
* @param {Function} [config.onChatMessage] - Callback for chat messages
* @param {Function} [config.onConnectionChange] - Callback for connection state changes
* @param {Function} [config.onTokenUpdate] - Callback for token state updates
* @param {Function} [config.onGameStateVerification] - Callback for game state verification
*/
constructor(config = {});
/**
* Initialize the client and connect to master server
* @returns {Promise<boolean>} - Whether initialization succeeded
*/
async init();
/**
* Connect to the master server
* @returns {Promise<boolean>} - Whether connection succeeded
*/
async connectToMasterServer();
/**
* Request server list from master server
* @returns {Promise<Array>} - List of available servers
*/
async requestServerList();
/**
* Connect to a game server
* @param {string} peerId - Server peer ID
* @returns {Promise<boolean>} - Whether connection succeeded
*/
async connectToServer(peerId);
/**
* Disconnect from the current server
* @returns {Promise<boolean>} - Whether disconnection succeeded
*/
async disconnect();
/**
* Send a chat message to the server
* @param {string} message - Message to send
* @returns {boolean} - Whether message was sent
*/
sendChatMessage(message);
/**
* Send a ping to the server
* @returns {Promise<number>} - Ping time in milliseconds
*/
async ping();
/**
* Get client status information
* @returns {Object} - Client status
*/
getStatus();
/**
* Get chat history
* @param {number} [limit=50] - Maximum number of messages to return
* @returns {Array} - Chat history
*/
getChatHistory(limit);
/**
* Mint new tokens
* @param {number} [count=1] - Number of tokens to mint
* @returns {Promise<Array>} - Minted tokens
*/
async mintTokens(count);
/**
* Get token inventory
* @returns {Object} - Token inventory information
*/
getTokenInventory();
/**
* Request game state verification
* @returns {Promise<Object>} - Verification result
*/
async verifyGameState();
/**
* Set event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}class BrowserGameServer {
/**
* Create a new browser game server
* @param {Object} config - Server configuration
* @param {string} [config.masterServer='localhost:27950'] - Master server address
* @param {string} [config.serverName='Browser Game Server'] - Server name
* @param {string} [config.map='browser_map'] - Map name
* @param {number} [config.maxPlayers=8] - Maximum number of players
* @param {boolean} [config.requireEntryToken=true] - Whether to require entry token
* @param {number} [config.stateTokenInterval=10000] - Interval for game state token updates
* @param {Function} [config.onClientConnect] - Callback for client connections
* @param {Function} [config.onClientDisconnect] - Callback for client disconnections
* @param {Function} [config.onChatMessage] - Callback for chat messages
* @param {Function} [config.onStatusUpdate] - Callback for status updates
*/
constructor(config = {});
/**
* Initialize the server and register with master server
* @returns {Promise<boolean>} - Whether initialization succeeded
*/
async init();
/**
* Connect to the master server
* @returns {Promise<boolean>} - Whether connection succeeded
*/
async connectToMasterServer();
/**
* Register server with master server
* @returns {Promise<boolean>} - Whether registration succeeded
*/
async registerServer();
/**
* Unregister server from master server
* @returns {Promise<boolean>} - Whether unregistration succeeded
*/
async unregisterServer();
/**
* Broadcast a message to all connected clients
* @param {Object} message - Message to broadcast
* @returns {boolean} - Whether broadcast succeeded
*/
broadcast(message);
/**
* Send a message to a specific client
* @param {string} clientId - Client ID
* @param {Object} message - Message to send
* @returns {boolean} - Whether message was sent
*/
sendToClient(clientId, message);
/**
* Kick a client from the server
* @param {string} clientId - Client ID
* @param {string} reason - Reason for kick
* @returns {boolean} - Whether kick succeeded
*/
kickClient(clientId, reason);
/**
* Get server status information
* @returns {Object} - Server status
*/
getStatus();
/**
* Get list of connected clients
* @returns {Array} - List of connected clients
*/
getClients();
/**
* End the game and distribute tokens to winner
* @param {string} winnerId - ID of the winning client
* @returns {Promise<boolean>} - Whether game end succeeded
*/
async endGame(winnerId);
/**
* Generate a game state token
* @returns {Promise<Object>} - Generated token
*/
async generateGameStateToken();
/**
* Start periodic game state verification
* @param {number} [intervalMs=10000] - Interval in milliseconds
*/
startStateVerification(intervalMs);
/**
* Stop periodic game state verification
*/
stopStateVerification();
/**
* Set event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}class P2PConnection {
/**
* Create a new P2P connection
* @param {Object} config - Connection configuration
* @param {string} [config.connectionId] - Connection ID
* @param {Object} [config.rtcConfig] - WebRTC configuration
* @param {boolean} [config.useWebSocket=true] - Whether to use WebSocket fallback
* @param {Function} [config.onOpen] - Callback for connection open
* @param {Function} [config.onMessage] - Callback for incoming messages
* @param {Function} [config.onClose] - Callback for connection close
* @param {Function} [config.onError] - Callback for errors
*/
constructor(config = {});
/**
* Connect to a peer
* @param {Object} signaling - Signaling channel
* @param {string} peerId - Peer ID to connect to
* @returns {Promise<boolean>} - Whether connection succeeded
*/
async connect(signaling, peerId);
/**
* Close the connection
*/
close();
/**
* Send a message
* @param {Object|string} message - Message to send
* @returns {boolean} - Whether message was sent
*/
send(message);
/**
* Check if the connection is open
* @returns {boolean} - Whether the connection is open
*/
isOpen();
/**
* Get connection information
* @returns {Object} - Connection information
*/
getConnectionInfo();
/**
* Set event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}class MasterServerConnector {
/**
* Create a new master server connector
* @param {Object} config - Connector configuration
* @param {string} [config.masterServer='localhost:27950'] - Master server address
* @param {Function} [config.onConnect] - Callback for connection
* @param {Function} [config.onDisconnect] - Callback for disconnection
* @param {Function} [config.onError] - Callback for errors
* @param {Function} [config.onMessage] - Callback for incoming messages
*/
constructor(config = {});
/**
* Connect to the master server
* @returns {Promise<boolean>} - Whether connection succeeded
*/
async connect();
/**
* Disconnect from the master server
*/
disconnect();
/**
* Send a message to the master server
* @param {Object} message - Message to send
* @returns {boolean} - Whether message was sent
*/
send(message);
/**
* Check if connected to the master server
* @returns {boolean} - Whether connected
*/
isConnected();
/**
* Set event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}The browser implementation will use the same message protocol as the Node.js implementation:
get_servers- Request server listconnect_to_server- Request connection to a serverdisconnect_from_server- Request disconnection from a serverconnection_success- Confirm successful connectionproxy_message- Proxy a message to a server
connected- Confirm connection to master serverserver_list- List of available serversproxy_connection- Connection established to serverproxy_data- Data from serverserver_disconnected- Server disconnecteddisconnect_ack- Disconnection acknowledgederror- Error message
chat- Chat messageping- Ping requesttoken:entry- Send entry tokenidentity:update- Update client identityrequest:game:state:token- Request game state token
welcome- Welcome message with server infochat- Chat messagepong- Ping responsekick- Kick messagetoken:entry:ack- Entry token acknowledgmenttoken:reward- Reward tokensidentity:update:ack- Identity update acknowledgmentgame:state:token- Game state token
Both client and server classes will implement an event system with the following events:
connect- Connected to master serverdisconnect- Disconnected from master serverserverList- Received server listserverConnect- Connected to a game serverserverDisconnect- Disconnected from a game serverchat- Received chat messageping- Ping responsetokenUpdate- Token state updatedgameStateVerification- Game state verification resulterror- Error
connect- Connected to master serverdisconnect- Disconnected from master serverclientConnect- Client connectedclientDisconnect- Client disconnectedchat- Received chat messagetokenEntry- Received entry tokengameStateUpdate- Game state updatederror- Error
class GameIntegration {
/**
* Create a new game integration
* @param {Object} config - Integration configuration
* @param {Object} [config.client] - Reference to BrowserGameClient instance
* @param {Object} [config.server] - Reference to BrowserGameServer instance
* @param {Function} [config.onGameStateUpdate] - Callback for game state updates
* @param {Function} [config.onTokenEvent] - Callback for token-related events
* @param {Function} [config.onPlayerJoin] - Callback for player join events
* @param {Function} [config.onPlayerLeave] - Callback for player leave events
* @param {Function} [config.onGameEnd] - Callback for game end
*/
constructor(config = {});
/**
* Initialize the game integration
* @param {Object} game - Reference to the actual game instance
* @returns {Promise<boolean>} - Whether initialization succeeded
*/
async init(game);
/**
* Get the current game state
* @returns {Object} - Game state object
*/
getGameState();
/**
* Update the token service with new game state
* @param {Object} gameState - New game state
* @returns {Promise<Object>} - Updated state token
*/
async updateGameState(gameState);
/**
* Handle player join with token verification
* @param {string} playerId - Player ID
* @param {Object} tokenFlow - Token flow for validation
* @returns {Promise<boolean>} - Whether join is allowed
*/
async handlePlayerJoin(playerId, tokenFlow);
/**
* Handle player leave
* @param {string} playerId - Player ID
* @returns {Promise<void>}
*/
async handlePlayerLeave(playerId);
/**
* End game and distribute tokens to winner
* @param {string} winnerId - ID of the winning player
* @returns {Promise<Object>} - Results of token distribution
*/
async endGame(winnerId);
/**
* Verify game state token
* @param {Object} tokenFlow - Token flow to verify
* @returns {Promise<Object>} - Verification result
*/
async verifyGameStateToken(tokenFlow);
/**
* Register an event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove an event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}class MockGame {
/**
* Create a new mock game instance
* @param {Object} config - Mock game configuration
* @param {boolean} [config.isServer=false] - Whether this is a server instance
* @param {number} [config.updateInterval=1000] - Game state update interval in ms
* @param {number} [config.playerCount=0] - Number of mock players to create
* @param {Function} [config.onStateChange] - Callback for state changes
*/
constructor(config = {});
/**
* Initialize the mock game
* @returns {Promise<boolean>} - Whether initialization succeeded
*/
async init();
/**
* Start the mock game simulation
* @returns {Promise<boolean>} - Whether start succeeded
*/
async start();
/**
* Stop the mock game simulation
*/
stop();
/**
* Add a player to the game
* @param {Object} player - Player data
* @returns {string} - Player ID
*/
addPlayer(player);
/**
* Remove a player from the game
* @param {string} playerId - Player ID
* @returns {boolean} - Whether removal succeeded
*/
removePlayer(playerId);
/**
* Get the current game state
* @returns {Object} - Game state
*/
getGameState();
/**
* Update the game state manually
* @param {Object} updates - State updates to apply
* @returns {Object} - Updated game state
*/
updateGameState(updates);
/**
* Generate a random game state update
* @returns {Object} - Random state update
*/
generateRandomUpdate();
/**
* End the game with a specific winner
* @param {string} [winnerId] - Winner ID (random if not specified)
* @returns {Object} - Game end result
*/
endGame(winnerId);
/**
* Register an event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
on(event, handler);
/**
* Remove an event handler
* @param {string} event - Event name
* @param {Function} handler - Event handler
*/
off(event, handler);
}The browser library will be bundled as follows:
uniquake-web.min.js- Minified bundle with all componentsuniquake-web.js- Non-minified bundle with all componentsuniquake-web-mock.js- Bundle including mock implementations for testing- Source maps for debugging
<!DOCTYPE html>
<html>
<head>
<title>UniQuake Web Mock Client</title>
<script src="uniquake-web-mock.js"></script>
</head>
<body>
<div id="server-list"></div>
<div id="chat"></div>
<div id="token-status"></div>
<div id="game-state"></div>
<button id="mint">Mint Tokens</button>
<button id="verify">Verify Game State</button>
<button id="connect">Connect to Server</button>
<input id="message" type="text" placeholder="Chat message...">
<button id="send">Send</button>
<script>
// Initialize mock game
const mockGame = new UniQuake.MockGame({
isServer: false,
updateInterval: 2000,
onStateChange: (state) => {
document.getElementById('game-state').textContent = JSON.stringify(state, null, 2);
}
});
// Initialize client
const client = new UniQuake.BrowserGameClient({
masterServer: 'localhost:27950',
playerName: 'WebPlayer',
mintTokens: 5,
onStatusUpdate: (status) => {
// Update UI with status
console.log('Status update:', status);
},
onChatMessage: (message) => {
// Update chat UI
const chatDiv = document.getElementById('chat');
chatDiv.innerHTML += `<p><strong>${message.from}:</strong> ${message.message}</p>`;
},
onTokenUpdate: (tokens) => {
// Update token UI
document.getElementById('token-status').textContent =
`Tokens: ${tokens.coins} (Value: ${tokens.value})`;
}
});
// Initialize game integration
const gameIntegration = new UniQuake.GameIntegration({
client: client,
onGameStateUpdate: (state) => {
console.log('Game state updated:', state);
},
onTokenEvent: (event) => {
console.log('Token event:', event);
}
});
// Initialize everything
async function initialize() {
await mockGame.init();
await client.init();
await gameIntegration.init(mockGame);
console.log('Client initialized with mock game');
// Start mock game simulation
mockGame.start();
// Request server list
client.requestServerList().then((servers) => {
const serverListDiv = document.getElementById('server-list');
serverListDiv.innerHTML = '';
servers.forEach((server, index) => {
const serverItem = document.createElement('div');
serverItem.textContent = `${index + 1}. ${server.name} - Players: ${server.players}/${server.maxPlayers}`;
serverItem.setAttribute('data-server-id', server.peerId);
serverItem.addEventListener('click', () => {
client.connectToServer(server.peerId);
});
serverListDiv.appendChild(serverItem);
});
});
}
initialize();
// Event handlers for UI
document.getElementById('mint').addEventListener('click', () => {
client.mintTokens(1).then((result) => {
console.log('Minted token:', result);
});
});
document.getElementById('verify').addEventListener('click', () => {
gameIntegration.verifyGameStateToken().then((result) => {
console.log('Verification result:', result);
alert(`Game state verification: ${result.verified ? 'Valid' : 'Invalid'}`);
});
});
document.getElementById('connect').addEventListener('click', () => {
// Connect to the first server in the list
client.requestServerList().then((servers) => {
if (servers.length > 0) {
client.connectToServer(servers[0].peerId);
} else {
alert('No servers available');
}
});
});
document.getElementById('send').addEventListener('click', () => {
const message = document.getElementById('message').value;
client.sendChatMessage(message);
document.getElementById('message').value = '';
});
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<title>UniQuake Web Mock Server</title>
<script src="uniquake-web-mock.js"></script>
</head>
<body>
<div id="server-status"></div>
<div id="client-list"></div>
<div id="token-pool"></div>
<div id="game-state"></div>
<button id="start">Start Server</button>
<button id="stop">Stop Server</button>
<button id="end-game">End Game</button>
<button id="update-state">Update Game State</button>
<script>
// Initialize mock game
const mockGame = new UniQuake.MockGame({
isServer: true,
updateInterval: 5000,
playerCount: 0, // Players will be added as clients connect
onStateChange: (state) => {
document.getElementById('game-state').textContent = JSON.stringify(state, null, 2);
}
});
// Initialize server
const server = new UniQuake.BrowserGameServer({
masterServer: 'localhost:27950',
serverName: 'Web Mock Server',
map: 'web_mock',
maxPlayers: 8,
requireEntryToken: true,
stateTokenInterval: 10000,
onClientConnect: (client) => {
console.log('Client connected:', client);
// Add player to mock game
mockGame.addPlayer({
id: client.id,
name: client.name || 'Player',
position: { x: Math.random() * 100, y: Math.random() * 100, z: Math.random() * 100 }
});
// Update UI
updateClientList();
},
onClientDisconnect: (client) => {
console.log('Client disconnected:', client);
// Remove player from mock game
mockGame.removePlayer(client.id);
// Update UI
updateClientList();
},
onChatMessage: (clientId, message) => {
console.log(`Chat from ${clientId}: ${message}`);
// Add to chat log in UI
const clientList = document.getElementById('client-list');
clientList.innerHTML += `<p><strong>${clientId}:</strong> ${message}</p>`;
}
});
// Initialize game integration
const gameIntegration = new UniQuake.GameIntegration({
server: server,
onGameStateUpdate: (state) => {
// Update token state when game state changes
console.log('Game state updated for token:', state);
},
onPlayerJoin: (playerId, tokenResult) => {
console.log(`Player ${playerId} joined with token:`, tokenResult);
}
});
// Initialize everything
async function initialize() {
await mockGame.init();
await server.init();
await gameIntegration.init(mockGame);
console.log('Server initialized with mock game');
updateServerStatus();
}
// Update client list in UI
function updateClientList() {
const clients = server.getClients();
const clientList = document.getElementById('client-list');
clientList.innerHTML = '<h3>Connected Clients</h3>';
clients.forEach(client => {
clientList.innerHTML += `<div>ID: ${client.id} - Name: ${client.name}</div>`;
});
}
// Update server status in UI
function updateServerStatus() {
const status = server.getStatus();
const statusDiv = document.getElementById('server-status');
statusDiv.innerHTML = `
<h3>Server Status</h3>
<div>Name: ${status.name}</div>
<div>Map: ${status.map}</div>
<div>Players: ${status.players}/${status.maxPlayers}</div>
<div>Registered: ${status.registered ? 'Yes' : 'No'}</div>
`;
// Update token pool info
const tokenPool = document.getElementById('token-pool');
tokenPool.innerHTML = `
<h3>Token Pool</h3>
<div>Entry Tokens: ${status.tokens?.entry || 0}</div>
<div>Total Value: ${status.tokens?.totalValue || '0'}</div>
`;
}
// Event handlers for UI
document.getElementById('start').addEventListener('click', () => {
if (!server.isRegistered()) {
server.registerServer().then(() => {
console.log('Server started and registered');
mockGame.start();
updateServerStatus();
});
} else {
alert('Server already registered');
}
});
document.getElementById('stop').addEventListener('click', () => {
server.unregisterServer().then(() => {
console.log('Server stopped');
mockGame.stop();
updateServerStatus();
});
});
document.getElementById('end-game').addEventListener('click', () => {
// Get clients
const clients = server.getClients();
// Select a winner (first client for demo)
if (clients.length > 0) {
gameIntegration.endGame(clients[0].id).then((result) => {
console.log('Game ended, tokens distributed:', result);
alert(`Game ended. Winner: ${clients[0].id}. Tokens distributed: ${result.tokenCount}`);
updateServerStatus();
});
} else {
alert('No clients connected to select a winner');
}
});
document.getElementById('update-state').addEventListener('click', () => {
// Generate random state update
const update = mockGame.generateRandomUpdate();
// Apply update
mockGame.updateGameState(update);
// This will trigger onStateChange and update the UI
console.log('Manual state update applied');
});
// Initialize the server
initialize();
</script>
</body>
</html>// Example of integrating with an actual game (Quake)
// Assume we have access to the game's global object
const quakeGame = window.quakeGame || {};
// Initialize our token components
const tokenService = new UniQuake.UniQuakeTokenService({
username: quakeGame.playerName || 'QuakePlayer'
});
const client = new UniQuake.BrowserGameClient({
masterServer: quakeGame.masterServerAddress || 'localhost:27950',
playerName: quakeGame.playerName || 'QuakePlayer',
mintTokens: 5
});
// Create game integration
const gameIntegration = new UniQuake.GameIntegration({
client: client,
onGameStateUpdate: (state) => {
// This will be called when the token service updates game state
console.log('Token state updated for game state:', state);
},
onTokenEvent: (event) => {
if (event.type === 'entry_token_required') {
// Show UI to the player explaining token requirement
quakeGame.showNotification('This server requires an entry token. Sending token...');
} else if (event.type === 'reward_received') {
// Show UI to the player that they received tokens
quakeGame.showNotification(`You received ${event.tokenCount} tokens for winning!`);
}
}
});
// Initialize components
async function initializeTokenSystem() {
await tokenService.init();
await client.init();
await gameIntegration.init(quakeGame);
console.log('Token system initialized for Quake');
// Register hooks into the game's lifecycle events
// When player joins a server
quakeGame.on('server_connect', async (serverInfo) => {
// Connect our client to the same server
await client.connectToServer(serverInfo.peerId);
});
// When game state updates (happens many times per second)
// We'll throttle this to every 2 seconds to avoid excessive token updates
let lastUpdate = 0;
quakeGame.on('game_state_update', (gameState) => {
const now = Date.now();
if (now - lastUpdate > 2000) {
lastUpdate = now;
gameIntegration.updateGameState(gameState);
}
});
// When game ends
quakeGame.on('game_end', (result) => {
if (result.isServer) {
// If we're the server, handle end game token distribution
gameIntegration.endGame(result.winnerId);
}
});
}
// Start initialization when the game is ready
if (quakeGame.isReady) {
initializeTokenSystem();
} else {
quakeGame.on('ready', initializeTokenSystem);
}The browser implementation will have the following dependencies:
- Unicity SDK Browser Bundle - Browser-compatible version of the Unicity tx-flow-engine SDK
- SimplePeer (or similar WebRTC library) - For WebRTC peer connections
- EventEmitter - For event handling
- CryptoJS - For cryptographic operations in the browser
All dependencies will be included in the bundled library for ease of use.
The library will be compatible with:
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
WebRTC fallback to WebSocket will be automatic for environments where WebRTC is not available.
To ensure good performance and reasonable bundle size:
- The library will be modular, allowing users to import only needed components
- All code will be minified and tree-shaken to reduce bundle size
- Async operations will be used for all network and crypto operations
- Web Workers will be used for intensive cryptographic operations
- IndexedDB will be used for token storage to avoid memory constraints
The target bundle size is under 500KB minified and gzipped.
For integrating with the actual Quake game in the browser, additional considerations include:
-
Non-Intrusive Integration: The token system should not modify the core game code but rather hook into existing events and APIs.
-
Performance Impact: Token operations should be performed off the main thread when possible to avoid impacting the game's frame rate.
-
User Experience: Token interactions should be minimally disruptive to gameplay (e.g., notifications that don't block the screen).
-
Game State Extraction: We'll need to define a standard way to extract the relevant game state for token operations without requiring deep game engine modifications.
-
Fallback Mechanisms: If token operations fail, the game should continue to function normally.
For the game integration to work properly, the game should expose:
interface GameState {
gameId: string; // Unique identifier for this game session
frame: number; // Current frame number
timestamp: number; // Current timestamp
players: { // Map of player information
[playerId: string]: {
position: {x: number, y: number, z: number};
health: number;
score: number;
// Other relevant player state
}
};
// Other game-specific state that needs verification
}The game should provide hooks for these events:
- Game Initialization: When the game is ready for token integration
- Server Connection: When connecting to a game server
- Player Join/Leave: When players enter or exit the game
- Game State Update: Regular updates to the game state
- Game End: When a match or round ends, including the winner information
For development and testing, we'll follow this process:
- Mock-First Development: Create and test the token system with mock implementations first
- Standalone Testing: Build standalone web pages that use the mock implementations
- API Validation: Validate the API design with mock implementations
- Game Integration: Once validated, integrate with the actual game
- Performance Testing: Test performance in the context of the actual game
- User Testing: Test the entire flow with real users
This approach allows us to develop and test the token functionality independently of the game, reducing development complexity and ensuring the system works correctly before integrating with the game.