Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions plugins/vite-plugin-web-share-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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) => {
Expand Down Expand Up @@ -74,17 +77,25 @@ 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)
res.statusCode = 500
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
}

Expand Down Expand Up @@ -131,4 +142,4 @@ export default function webShareTesterPlugin(options = {}) {
}

// Named export for plugin options
export { webShareTesterPlugin }
export { webShareTesterPlugin }
80 changes: 43 additions & 37 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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')))
}

Expand All @@ -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')
Expand All @@ -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
Expand Down Expand Up @@ -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)
})
}

Expand Down Expand Up @@ -201,4 +207,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
await server.stop()
process.exit(0)
})
}
}
52 changes: 41 additions & 11 deletions src/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: '📱' },
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -182,7 +212,7 @@ export function App() {
) : (
<button
class="connection-btn connect"
onClick={connectWebSocket}
onClick={startLiveMode}
>
Connect Live Mode
</button>
Expand Down