UNPKG

ws402

Version:

WebSocket implementation of X402 protocol for pay-as-you-go digital resources with automatic refunds

131 lines (130 loc) 4.84 kB
"use strict"; // src/middlewareHTTP.ts // Extended WS402 for HTTP resources with WebSocket time tracking Object.defineProperty(exports, "__esModule", { value: true }); exports.WS402HTTPMiddleware = void 0; exports.createHTTPResourceRoute = createHTTPResourceRoute; /** * Middleware for serving HTTP resources with WS402 time tracking * * Use case: PDFs, images, videos served via HTTP but tracked via WebSocket */ class WS402HTTPMiddleware { constructor(ws402) { /** * Middleware to protect HTTP resources * Returns 402 if no valid WS402 session exists */ this.protectHTTPResource = () => { return (req, res, next) => { const sessionToken = req.query.sessionToken; const resourceId = req.params.resourceId || req.query.resourceId; if (!sessionToken) { return res.status(402).json({ error: 'Payment Required', message: 'Establish WS402 session first', instructions: { step1: 'Connect to WebSocket', step2: 'Send payment proof', step3: 'Receive session token', step4: 'Use token to access resource', } }); } // Verify session exists and is active const httpSession = this.activeHTTPSessions.get(sessionToken); if (!httpSession) { return res.status(403).json({ error: 'Invalid or expired session', message: 'Session not found or has ended' }); } // Mark resource as accessed httpSession.accessed = true; // Add session info to request for logging req.ws402Session = httpSession; // Allow access to resource next(); }; }; this.ws402 = ws402; this.activeHTTPSessions = new Map(); } /** * Register a new HTTP session (called after WS402 payment verification) */ registerHTTPSession(sessionId, resourceId) { const sessionToken = this.generateToken(); this.activeHTTPSessions.set(sessionToken, { sessionId, resourceId, startTime: Date.now(), accessed: false, }); return sessionToken; } /** * Remove HTTP session (called when WS402 session ends) */ removeHTTPSession(sessionId) { // Find and remove by sessionId for (const [token, session] of this.activeHTTPSessions.entries()) { if (session.sessionId === sessionId) { this.activeHTTPSessions.delete(token); break; } } } /** * Get all active HTTP sessions */ getActiveHTTPSessions() { return Array.from(this.activeHTTPSessions.entries()).map(([token, session]) => ({ token, ...session, elapsedTime: Date.now() - session.startTime, })); } /** * Generate unique session token */ generateToken() { return `http_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } exports.WS402HTTPMiddleware = WS402HTTPMiddleware; /** * Helper to create Express route for HTTP resource with WS402 tracking */ function createHTTPResourceRoute(ws402, httpMiddleware, resourceGetter) { return [ httpMiddleware.protectHTTPResource(), (req, res) => { const resourceId = req.params.resourceId || req.query.resourceId; const session = req.ws402Session; console.log(`📄 Serving resource ${resourceId} for session ${session.sessionId}`); // Get and serve the resource const resource = resourceGetter(resourceId); if (!resource) { return res.status(404).json({ error: 'Resource not found' }); } // Set appropriate headers res.setHeader('Content-Type', resource.contentType || 'application/octet-stream'); res.setHeader('Content-Disposition', `inline; filename="${resource.filename}"`); res.setHeader('X-WS402-Session', session.sessionId); // Send the resource if (typeof resource.data === 'string') { res.send(resource.data); } else if (Buffer.isBuffer(resource.data)) { res.send(resource.data); } else if (resource.stream) { resource.stream.pipe(res); } else { res.json(resource.data); } } ]; }