diff --git a/websocket-bug-demo/.gitignore b/websocket-bug-demo/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/websocket-bug-demo/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/websocket-bug-demo/Dockerfile b/websocket-bug-demo/Dockerfile
new file mode 100644
index 0000000..8608dfd
--- /dev/null
+++ b/websocket-bug-demo/Dockerfile
@@ -0,0 +1,12 @@
+FROM node:20-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 7654 6543
+
+CMD ["node", "server.js"]
diff --git a/websocket-bug-demo/README.md b/websocket-bug-demo/README.md
new file mode 100644
index 0000000..412d1ee
--- /dev/null
+++ b/websocket-bug-demo/README.md
@@ -0,0 +1,31 @@
+# WebSocket Bug Demo
+
+A demo project to reproduce a WebSocket connection bug in Zen browser.
+
+## The Bug
+
+When opening a web app served from `localhost:7654` and attempting to connect to a WebSocket server on `localhost:6543` (different port), the connection gets blocked by the browser.
+
+This issue appears to originate from Zen browser's Advanced Tracking Protection functionality, though the exact cause is still uncertain.
+
+## Quick Start
+
+### Option 1: Docker (recommended)
+
+```bash
+docker compose up --build
+```
+
+### Option 2: Local
+
+```bash
+npm install
+npm start
+```
+
+## Usage
+
+Open http://localhost:7654 in your browser and click "Connect".
+
+- **HTTP server**: `localhost:7654` - serves the client UI
+- **WebSocket server**: `localhost:6543` - sends pings every 5 seconds
diff --git a/websocket-bug-demo/docker-compose.yml b/websocket-bug-demo/docker-compose.yml
new file mode 100644
index 0000000..3d8a425
--- /dev/null
+++ b/websocket-bug-demo/docker-compose.yml
@@ -0,0 +1,7 @@
+services:
+ websocket-demo:
+ build: .
+ ports:
+ - "7654:7654"
+ - "6543:6543"
+ restart: unless-stopped
diff --git a/websocket-bug-demo/index.html b/websocket-bug-demo/index.html
new file mode 100644
index 0000000..176fe07
--- /dev/null
+++ b/websocket-bug-demo/index.html
@@ -0,0 +1,523 @@
+
+
+
+
+
+ WebSocket Bug Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
WebSocket Bug Demo
+
+
+
+
+
+
+
+
+
diff --git a/websocket-bug-demo/package-lock.json b/websocket-bug-demo/package-lock.json
new file mode 100644
index 0000000..65ef04c
--- /dev/null
+++ b/websocket-bug-demo/package-lock.json
@@ -0,0 +1,36 @@
+{
+ "name": "websocket-bug-demo",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "websocket-bug-demo",
+ "version": "1.0.0",
+ "dependencies": {
+ "ws": "^8.14.2"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/websocket-bug-demo/package.json b/websocket-bug-demo/package.json
new file mode 100644
index 0000000..33e4bf2
--- /dev/null
+++ b/websocket-bug-demo/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "websocket-bug-demo",
+ "version": "1.0.0",
+ "description": "Demo project to demonstrate WebSocket connection bug",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js"
+ },
+ "dependencies": {
+ "ws": "^8.14.2"
+ }
+}
diff --git a/websocket-bug-demo/server.js b/websocket-bug-demo/server.js
new file mode 100644
index 0000000..f4f4930
--- /dev/null
+++ b/websocket-bug-demo/server.js
@@ -0,0 +1,94 @@
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const { WebSocketServer } = require('ws');
+
+// HTTP server to serve index.html on port 7654
+const httpServer = http.createServer((req, res) => {
+ if (req.url === '/' || req.url === '/index.html') {
+ const filePath = path.join(__dirname, 'index.html');
+ fs.readFile(filePath, (err, content) => {
+ if (err) {
+ res.writeHead(500);
+ res.end('Error loading index.html');
+ return;
+ }
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(content);
+ });
+ } else {
+ res.writeHead(404);
+ res.end('Not Found');
+ }
+});
+
+httpServer.listen(7654, () => {
+ console.log('HTTP server running at http://localhost:7654');
+});
+
+// WebSocket server on port 6543
+const wss = new WebSocketServer({ port: 6543 });
+
+console.log('WebSocket server running at ws://localhost:6543');
+
+wss.on('connection', (ws, req) => {
+ const clientId = Date.now();
+ console.log(`Client ${clientId} connected`);
+
+ // Send initial welcome message
+ ws.send(JSON.stringify({
+ type: 'welcome',
+ message: 'Connected to WebSocket server',
+ clientId: clientId,
+ timestamp: new Date().toISOString()
+ }));
+
+ // Send ping every 5 seconds
+ const pingInterval = setInterval(() => {
+ if (ws.readyState === ws.OPEN) {
+ ws.send(JSON.stringify({
+ type: 'ping',
+ timestamp: new Date().toISOString(),
+ serverTime: Date.now()
+ }));
+ console.log(`Sent ping to client ${clientId}`);
+ }
+ }, 5000);
+
+ // Handle incoming messages
+ ws.on('message', (data) => {
+ console.log(`Received from client ${clientId}:`, data.toString());
+ try {
+ const parsed = JSON.parse(data);
+ if (parsed.type === 'pong') {
+ console.log(`Received pong from client ${clientId}`);
+ }
+ } catch (e) {
+ console.log(`Raw message from client ${clientId}:`, data.toString());
+ }
+ });
+
+ // Handle close
+ ws.on('close', (code, reason) => {
+ console.log(`Client ${clientId} disconnected. Code: ${code}, Reason: ${reason}`);
+ clearInterval(pingInterval);
+ });
+
+ // Handle errors
+ ws.on('error', (error) => {
+ console.error(`Error with client ${clientId}:`, error);
+ clearInterval(pingInterval);
+ });
+});
+
+// Handle server errors
+wss.on('error', (error) => {
+ console.error('WebSocket server error:', error);
+});
+
+process.on('SIGINT', () => {
+ console.log('\nShutting down servers...');
+ wss.close();
+ httpServer.close();
+ process.exit(0);
+});