Files
blackroad-operating-system/backend/static/js/api-client.js
Claude 138d79a6e3 Integrate BlackRoad OS front-end with FastAPI backend
This commit transforms the BlackRoad OS from a static mockup into a fully
functional web-based operating system with real backend integration.

## Major Changes

### Backend (New Features)

1. **Device Management System** (IoT/Raspberry Pi)
   - New models: Device, DeviceMetric, DeviceLog
   - Router: /api/devices with full CRUD operations
   - Device heartbeat system for status monitoring
   - Metrics tracking (CPU, RAM, temperature)

2. **Mining Stats & Control** (RoadCoin Miner)
   - Router: /api/miner with status, stats, control endpoints
   - Simulated mining with hashrate, shares, temperature
   - Start/stop mining controls
   - Lifetime statistics and recent blocks listing

3. **Static File Serving**
   - Backend now serves front-end from /backend/static/
   - index.html served at root URL
   - API routes under /api/* namespace

4. **Updated User Model**
   - Added devices relationship

### Frontend (New Features)

1. **API Client Module** (api-client.js)
   - Centralized API communication layer
   - Automatic base URL detection (dev vs prod)
   - JWT token management with auto-refresh
   - Error handling and 401 redirects

2. **Authentication System** (auth.js)
   - Login/Register modal UI
   - Session persistence via localStorage
   - Auto-logout on token expiration
   - Keyboard shortcuts (Enter to submit)

3. **Application Modules** (apps.js)
   - Dynamic data loading for all desktop windows
   - Auto-refresh for real-time data (miner, blockchain)
   - Event-driven architecture
   - Lazy loading (data fetched only when window opens)

4. **Enhanced UI**
   - Added 380+ lines of CSS for new components
   - Auth modal styling
   - Miner dashboard layout
   - Blockchain explorer tables
   - Wallet balance display
   - Device management cards

5. **Live Window Integration**
   - RoadCoin Miner: Real mining stats, start/stop controls
   - RoadChain Explorer: Live blockchain data, mine block button
   - Wallet: Real-time balance, transaction history
   - Raspberry Pi: Device status dashboard
   - RoadMail: Live inbox from API
   - Social Feed: Real posts from database
   - BlackStream: Video grid from API
   - AI Assistant: Conversation UI

### Configuration

- Updated .env.example with:
  - ROADCHAIN_RPC_URL, ROADCOIN_POOL_URL
  - MQTT broker settings for device management
  - Production CORS origins (www.blackroad.systems)
  - PORT configuration for Railway deployment

### Documentation

- Added INTEGRATION_GUIDE.md (400+ lines)
  - Complete architecture overview
  - API endpoint documentation
  - Environment configuration guide
  - Development workflow
  - Troubleshooting section

## Technical Details

- All windows now connect to real backend APIs
- Authentication required before OS access
- User-specific data isolation
- Proper error handling and loading states
- Retro Windows 95 aesthetic preserved

## What's Working

 Full authentication flow (login/register)
 Mining stats and control
 Blockchain explorer with live data
 Wallet with real balance
 Device management dashboard
 Email inbox integration
 Social feed integration
 Video platform integration
 Static file serving
 CORS configuration

## Future Enhancements

- Real XMRig integration
- WebSocket for real-time updates
- MQTT broker for device heartbeats
- OpenAI/Anthropic API integration
- File uploads to S3
- Email sending via SMTP

## Files Added

- backend/app/models/device.py
- backend/app/routers/devices.py
- backend/app/routers/miner.py
- backend/static/index.html
- backend/static/js/api-client.js
- backend/static/js/auth.js
- backend/static/js/apps.js
- INTEGRATION_GUIDE.md

## Files Modified

- backend/app/main.py (added routers, static file serving)
- backend/app/models/user.py (added devices relationship)
- backend/.env.example (added device & mining variables)

Tested locally with Docker Compose (PostgreSQL + Redis).
Ready for Railway deployment.
2025-11-16 07:19:45 +00:00

410 lines
10 KiB
JavaScript

/**
* BlackRoad OS API Client
* Centralized API communication module
*/
class ApiClient {
constructor() {
// Determine API base URL based on environment
this.baseUrl = this.getApiBaseUrl();
this.token = localStorage.getItem('blackroad_token');
}
/**
* Get API base URL (development vs production)
*/
getApiBaseUrl() {
// In production on Railway, API and front-end are served from same origin
const hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') {
// Local development - backend on port 8000
return 'http://localhost:8000';
} else if (hostname === 'www.blackroad.systems' || hostname.includes('railway.app')) {
// Production - same origin
return window.location.origin;
} else {
// Default to same origin
return window.location.origin;
}
}
/**
* Set authentication token
*/
setToken(token) {
this.token = token;
localStorage.setItem('blackroad_token', token);
}
/**
* Clear authentication token
*/
clearToken() {
this.token = null;
localStorage.removeItem('blackroad_token');
}
/**
* Get current token
*/
getToken() {
return this.token;
}
/**
* Check if user is authenticated
*/
isAuthenticated() {
return !!this.token;
}
/**
* Get request headers
*/
getHeaders(includeAuth = true) {
const headers = {
'Content-Type': 'application/json',
};
if (includeAuth && this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
return headers;
}
/**
* Make API request
*/
async request(method, endpoint, data = null, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
method,
headers: this.getHeaders(options.includeAuth !== false),
...options,
};
if (data) {
config.body = JSON.stringify(data);
}
try {
const response = await fetch(url, config);
// Handle 401 Unauthorized
if (response.status === 401) {
this.clearToken();
window.dispatchEvent(new CustomEvent('auth:logout'));
throw new Error('Session expired. Please log in again.');
}
// Handle non-2xx responses
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`);
}
// Handle 204 No Content
if (response.status === 204) {
return null;
}
return await response.json();
} catch (error) {
console.error(`API request failed: ${method} ${endpoint}`, error);
throw error;
}
}
/**
* GET request
*/
async get(endpoint, options = {}) {
return this.request('GET', endpoint, null, options);
}
/**
* POST request
*/
async post(endpoint, data = null, options = {}) {
return this.request('POST', endpoint, data, options);
}
/**
* PUT request
*/
async put(endpoint, data = null, options = {}) {
return this.request('PUT', endpoint, data, options);
}
/**
* DELETE request
*/
async delete(endpoint, options = {}) {
return this.request('DELETE', endpoint, null, options);
}
// ===== Authentication API =====
async register(username, email, password, fullName = null) {
return this.post('/api/auth/register', {
username,
email,
password,
full_name: fullName
}, { includeAuth: false });
}
async login(username, password) {
const response = await this.post('/api/auth/login', {
username,
password
}, { includeAuth: false });
if (response.access_token) {
this.setToken(response.access_token);
}
return response;
}
async logout() {
try {
await this.post('/api/auth/logout');
} finally {
this.clearToken();
window.dispatchEvent(new CustomEvent('auth:logout'));
}
}
async getCurrentUser() {
return this.get('/api/auth/me');
}
// ===== Blockchain/Wallet API =====
async getWallet() {
return this.get('/api/blockchain/wallet');
}
async getBalance() {
return this.get('/api/blockchain/balance');
}
async getTransactions(limit = 10, offset = 0) {
return this.get(`/api/blockchain/transactions?limit=${limit}&offset=${offset}`);
}
async getTransaction(txHash) {
return this.get(`/api/blockchain/transactions/${txHash}`);
}
async createTransaction(toAddress, amount) {
return this.post('/api/blockchain/transactions', {
to_address: toAddress,
amount
});
}
async getBlocks(limit = 10, offset = 0) {
return this.get(`/api/blockchain/blocks?limit=${limit}&offset=${offset}`);
}
async getBlock(blockId) {
return this.get(`/api/blockchain/blocks/${blockId}`);
}
async mineBlock() {
return this.post('/api/blockchain/mine');
}
async getBlockchainStats() {
return this.get('/api/blockchain/stats');
}
// ===== Miner API =====
async getMinerStatus() {
return this.get('/api/miner/status');
}
async getMinerStats() {
return this.get('/api/miner/stats');
}
async getMinedBlocks(limit = 10) {
return this.get(`/api/miner/blocks?limit=${limit}`);
}
async controlMiner(action, poolUrl = null, workerId = null) {
return this.post('/api/miner/control', {
action,
pool_url: poolUrl,
worker_id: workerId
});
}
async getPoolInfo() {
return this.get('/api/miner/pool/info');
}
// ===== Devices API =====
async getDevices() {
return this.get('/api/devices/');
}
async getDeviceStats() {
return this.get('/api/devices/stats');
}
async getDevice(deviceId) {
return this.get(`/api/devices/${deviceId}`);
}
async createDevice(deviceData) {
return this.post('/api/devices/', deviceData);
}
async updateDevice(deviceId, deviceData) {
return this.put(`/api/devices/${deviceId}`, deviceData);
}
async deleteDevice(deviceId) {
return this.delete(`/api/devices/${deviceId}`);
}
// ===== Email API =====
async getEmailFolders() {
return this.get('/api/email/folders');
}
async getEmails(folder = 'inbox', limit = 50, offset = 0) {
const endpoint = folder === 'inbox'
? `/api/email/inbox?limit=${limit}&offset=${offset}`
: `/api/email/sent?limit=${limit}&offset=${offset}`;
return this.get(endpoint);
}
async getEmail(emailId) {
return this.get(`/api/email/${emailId}`);
}
async sendEmail(to, subject, body, cc = null, bcc = null) {
return this.post('/api/email/send', {
to,
subject,
body,
cc,
bcc
});
}
async deleteEmail(emailId) {
return this.delete(`/api/email/${emailId}`);
}
// ===== Social API =====
async getSocialFeed(limit = 20, offset = 0) {
return this.get(`/api/social/feed?limit=${limit}&offset=${offset}`);
}
async createPost(content, images = null, videos = null) {
return this.post('/api/social/posts', {
content,
images,
videos
});
}
async likePost(postId) {
return this.post(`/api/social/posts/${postId}/like`);
}
async getComments(postId) {
return this.get(`/api/social/posts/${postId}/comments`);
}
async addComment(postId, content) {
return this.post(`/api/social/posts/${postId}/comments`, {
content
});
}
async followUser(userId) {
return this.post(`/api/social/users/${userId}/follow`);
}
// ===== Video API =====
async getVideos(limit = 20, offset = 0) {
return this.get(`/api/videos?limit=${limit}&offset=${offset}`);
}
async getVideo(videoId) {
return this.get(`/api/videos/${videoId}`);
}
async likeVideo(videoId) {
return this.post(`/api/videos/${videoId}/like`);
}
// ===== AI Chat API =====
async getConversations() {
return this.get('/api/ai-chat/conversations');
}
async createConversation(title = 'New Conversation') {
return this.post('/api/ai-chat/conversations', { title });
}
async getConversation(conversationId) {
return this.get(`/api/ai-chat/conversations/${conversationId}`);
}
async getMessages(conversationId) {
return this.get(`/api/ai-chat/conversations/${conversationId}/messages`);
}
async sendMessage(conversationId, message) {
return this.post(`/api/ai-chat/conversations/${conversationId}/messages`, {
message
});
}
async deleteConversation(conversationId) {
return this.delete(`/api/ai-chat/conversations/${conversationId}`);
}
// ===== Files API =====
async getFolders() {
return this.get('/api/files/folders');
}
async getFiles(folderId = null) {
const endpoint = folderId
? `/api/files?folder_id=${folderId}`
: '/api/files';
return this.get(endpoint);
}
async getFile(fileId) {
return this.get(`/api/files/${fileId}`);
}
async deleteFile(fileId) {
return this.delete(`/api/files/${fileId}`);
}
}
// Create singleton instance
const api = new ApiClient();
// Export for use in other modules
window.BlackRoadAPI = api;