diff --git a/plugins/vite-plugin-web-share-tester.js b/plugins/vite-plugin-web-share-tester.js index 13b5a65..0d73d2f 100644 --- a/plugins/vite-plugin-web-share-tester.js +++ b/plugins/vite-plugin-web-share-tester.js @@ -25,13 +25,16 @@ export default function webShareTesterPlugin(options = {}) { let testerServer = null let actualPort = port + let shouldRun = false return { name: 'vite-plugin-web-share-tester', configResolved(config) { // Only enable in development mode - if (config.command !== 'serve' || !enabled) { + shouldRun = config.command === 'serve' && enabled + + if (!shouldRun) { return } @@ -40,13 +43,13 @@ export default function webShareTesterPlugin(options = {}) { buildStart() { // Start the testing server when Vite starts - if (enabled) { + if (shouldRun) { this.startTesterServer() } }, configureServer(server) { - if (!enabled) return + if (!shouldRun) return // Add middleware to serve the client shim script server.middlewares.use('/web-share-tester-shim.js', (req, res, next) => { @@ -74,6 +77,7 @@ export default function webShareTesterPlugin(options = {}) { ) res.setHeader('Content-Type', 'application/javascript') + res.setHeader('Cache-Control', 'no-store') res.end(shimContent) } catch (error) { console.error('❌ Failed to serve client shim:', error) @@ -81,10 +85,17 @@ export default function webShareTesterPlugin(options = {}) { res.end('Failed to load Web Share Tester shim') } }) + + server.httpServer?.once('close', async () => { + if (testerServer) { + await testerServer.stop() + testerServer = null + } + }) }, transformIndexHtml(html, context) { - if (!enabled || context.server?.config.command !== 'serve') { + if (!shouldRun || context.server?.config.command !== 'serve') { return html } @@ -131,4 +142,4 @@ export default function webShareTesterPlugin(options = {}) { } // Named export for plugin options -export { webShareTesterPlugin } \ No newline at end of file +export { webShareTesterPlugin } diff --git a/server/index.js b/server/index.js index 5f6e63e..28c395a 100644 --- a/server/index.js +++ b/server/index.js @@ -10,11 +10,16 @@ const __dirname = path.dirname(__filename) class WebShareTesterServer { constructor(options = {}) { - this.port = options.port || 3001 + this.port = Number(options.port) || 3001 + this.maxPort = Number(options.maxPort) || this.port + 9 this.app = express() this.server = createServer(this.app) this.wss = new WebSocketServer({ server: this.server }) this.clients = new Set() + + this.wss.on('error', (error) => { + console.error('❌ WebSocket server error:', error) + }) this.setupMiddleware() this.setupRoutes() @@ -23,7 +28,7 @@ class WebShareTesterServer { setupMiddleware() { this.app.use(cors()) - this.app.use(express.json()) + this.app.use(express.json({ limit: '100kb' })) this.app.use(express.static(path.join(__dirname, '../dist'))) } @@ -33,6 +38,13 @@ class WebShareTesterServer { res.json({ status: 'ok', timestamp: new Date().toISOString() }) }) + this.app.get('/api/config', (req, res) => { + res.json({ + port: this.port, + wsUrl: `${req.protocol === 'https' ? 'wss' : 'ws'}://${req.get('host')}` + }) + }) + // Serve the client shim script this.app.get('/web-share-tester-shim.js', (req, res) => { const shimPath = path.join(__dirname, 'client-shim.js') @@ -50,6 +62,10 @@ class WebShareTesterServer { // Endpoint to receive intercepted share data this.app.post('/api/share', (req, res) => { const shareData = req.body + if (!shareData || typeof shareData !== 'object' || Array.isArray(shareData)) { + return res.status(400).json({ error: 'Share payload must be an object' }) + } + console.log('📤 Intercepted share data:', shareData) // Broadcast to all connected WebSocket clients @@ -130,44 +146,34 @@ class WebShareTesterServer { } async start() { - return new Promise((resolve, reject) => { - // Try the specified port first - this.server.listen(this.port, (error) => { - if (error) { - if (error.code === 'EADDRINUSE') { - console.log(`⚠️ Port ${this.port} is busy, trying next available port...`) - this.findAvailablePort().then(resolve).catch(reject) - } else { - reject(error) - } - } else { - console.log(`🚀 Web Share Tester server running on http://localhost:${this.port}`) - console.log(`🔗 WebSocket server running on ws://localhost:${this.port}`) - resolve(this.port) - } - }) - }) + return this.listenOnPort(this.port) } - async findAvailablePort() { + async listenOnPort(port) { return new Promise((resolve, reject) => { - const tryPort = (port) => { - this.server.listen(port, (error) => { - if (error) { - if (error.code === 'EADDRINUSE' && port < 3010) { - tryPort(port + 1) - } else { - reject(error) - } - } else { - this.port = port - console.log(`🚀 Web Share Tester server running on http://localhost:${port}`) - console.log(`🔗 WebSocket server running on ws://localhost:${port}`) - resolve(port) - } - }) + const handleListening = () => { + this.server.off('error', handleError) + this.port = port + console.log(`🚀 Web Share Tester server running on http://localhost:${port}`) + console.log(`🔗 WebSocket server running on ws://localhost:${port}`) + resolve(port) + } + + const handleError = (error) => { + this.server.off('listening', handleListening) + + if (error.code === 'EADDRINUSE' && port < this.maxPort) { + console.log(`⚠️ Port ${port} is busy, trying ${port + 1}...`) + resolve(this.listenOnPort(port + 1)) + return + } + + reject(error) } - tryPort(this.port + 1) + + this.server.once('listening', handleListening) + this.server.once('error', handleError) + this.server.listen(port) }) } @@ -201,4 +207,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { await server.stop() process.exit(0) }) -} \ No newline at end of file +} diff --git a/src/app.jsx b/src/app.jsx index 49231cd..28fd117 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -17,6 +17,7 @@ export function App() { const [wsConnected, setWsConnected] = useState(false) const [lastInterceptedAt, setLastInterceptedAt] = useState(null) const wsRef = useRef(null) + const shouldReconnectRef = useRef(true) const platforms = [ { id: 'ios', name: 'iOS', icon: '📱' }, @@ -42,13 +43,29 @@ export function App() { } // WebSocket connection management - const connectWebSocket = () => { + const getWebSocketUrls = () => { + const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + const host = window.location.hostname || 'localhost' + const currentPort = window.location.port ? `:${window.location.port}` : '' + const currentOriginUrl = `${wsProtocol}//${host}${currentPort}` + const legacyDefaultUrl = `${wsProtocol}//${host}:3001` + + return Array.from(new Set([currentOriginUrl, legacyDefaultUrl])) + } + + const connectWebSocket = (attemptIndex = 0) => { if (wsRef.current?.readyState === WebSocket.OPEN) return + if (!shouldReconnectRef.current) return try { - const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' - const wsHost = window.location.hostname || 'localhost' - const wsUrl = `${wsProtocol}//${wsHost}:3001` + const urls = getWebSocketUrls() + const wsUrl = urls[attemptIndex] + + if (!wsUrl) { + console.warn('⚠️ No WebSocket URLs left to try') + setWsConnected(false) + return + } console.log('🔗 Connecting to WebSocket:', wsUrl) const ws = new WebSocket(wsUrl) @@ -91,25 +108,29 @@ export function App() { ws.onclose = () => { console.log('🔌 WebSocket disconnected') setWsConnected(false) - setIsLiveMode(false) wsRef.current = null + + if (shouldReconnectRef.current && attemptIndex + 1 < urls.length) { + connectWebSocket(attemptIndex + 1) + } } ws.onerror = (error) => { console.error('❌ WebSocket error:', error) - setWsConnected(false) - setIsLiveMode(false) + if (attemptIndex + 1 < urls.length) { + connectWebSocket(attemptIndex + 1) + } } wsRef.current = ws } catch (error) { console.error('❌ Failed to connect WebSocket:', error) setWsConnected(false) - setIsLiveMode(false) } } const disconnectWebSocket = () => { + shouldReconnectRef.current = false if (wsRef.current) { wsRef.current.close() wsRef.current = null @@ -118,22 +139,31 @@ export function App() { setIsLiveMode(false) } + const startLiveMode = () => { + shouldReconnectRef.current = true + setIsLiveMode(true) + connectWebSocket() + } + // Effect to manage WebSocket connection useEffect(() => { // Try to connect on component mount - connectWebSocket() + shouldReconnectRef.current = true + setIsLiveMode(true) + startLiveMode() // Cleanup on unmount return () => { if (wsRef.current) { wsRef.current.close() } + shouldReconnectRef.current = false } }, []) // Reconnection logic useEffect(() => { - if (!wsConnected && isLiveMode) { + if (!wsConnected && isLiveMode && shouldReconnectRef.current) { const reconnectInterval = setInterval(() => { console.log('🔄 Attempting to reconnect WebSocket...') connectWebSocket() @@ -182,7 +212,7 @@ export function App() { ) : (