diff --git a/.gitignore b/.gitignore
index 5ef6a52..365a670 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,14 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
+# editor / local tooling
+.vscode/
+
+# local database artifacts
+*.db
+*.db-shm
+*.db-wal
+
# env files (can opt-in for committing if needed)
.env*
diff --git a/README.md b/README.md
index e215bc4..1664c2c 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,69 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
+# Visio - Visioconference WebRTC
-## Getting Started
+Visio est une application de visioconference simple, rapide et sans inscription.
+Le projet est construit avec Next.js, React et WebRTC pour offrir des appels peer-to-peer.
-First, run the development server:
+## Site en production
+
+Application en ligne: https://visio.arthurp.fr
+
+Si vous mentionnez ce projet, vous pouvez faire un lien direct vers:
+https://visio.arthurp.fr
+
+## Fonctionnalites
+
+- Creation de salle en un clic
+- Partage par lien unique
+- Aucun compte requis
+- Nom utilisateur memorise localement
+- Controle micro/camera
+- Fermeture automatique des salles inactives
+- Interface en francais
+
+## Stack technique
+
+- Frontend: Next.js 16 + React 19
+- Backend: serveur Node.js personnalise
+- Temps reel: Socket.io
+- Video/audio: WebRTC (simple-peer)
+- Persistance: SQLite (better-sqlite3)
+
+## Installation locale
+
+Prerequis:
+
+- Node.js 18+
+- npm
+
+Commandes:
```bash
+npm install
npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+Application disponible ensuite sur:
+http://localhost:3000
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+## Scripts
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
+- `npm run dev` : demarrage en developpement
+- `npm run build` : build de production
+- `npm start` : lancement serveur de production
+- `npm run lint` : verification ESLint
-## Learn More
+## Variables d'environnement
-To learn more about Next.js, take a look at the following resources:
+- `PORT` : port HTTP du serveur (defaut `3000`)
+- `NODE_ENV` : `development` ou `production`
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+## Deploiement
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+```bash
+npm run build
+npm start
+```
-## Deploy on Vercel
+## Licence
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+MIT
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5a67c7f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,20 @@
+services:
+ visio:
+ image: node:20-alpine
+ container_name: visio-app
+ working_dir: /app
+ volumes:
+ - ./:/app
+ - /app/node_modules
+ ports:
+ - "3010:3000"
+ environment:
+ - PORT=3000
+ command: sh -c "npm install && npm run build && npm start"
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
diff --git a/package-lock.json b/package-lock.json
index 000f4b5..901c342 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,15 +8,22 @@
"name": "visio",
"version": "0.1.0",
"dependencies": {
+ "better-sqlite3": "^12.6.2",
"next": "16.1.6",
"react": "19.2.3",
- "react-dom": "19.2.3"
+ "react-dom": "19.2.3",
+ "simple-peer": "^9.11.1",
+ "socket.io": "^4.8.3",
+ "socket.io-client": "^4.8.3",
+ "uuid": "^13.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
+ "@types/better-sqlite3": "^7.6.13",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "@types/uuid": "^10.0.0",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"tailwindcss": "^4",
@@ -1234,6 +1241,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1525,6 +1538,25 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.13",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
+ "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1550,7 +1582,6 @@
"version": "20.19.31",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz",
"integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -1577,6 +1608,13 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
@@ -2116,6 +2154,19 @@
"win32"
]
},
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -2410,6 +2461,35 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@@ -2419,6 +2499,64 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
+ "node_modules/better-sqlite3": {
+ "version": "12.6.2",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
+ "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ },
+ "engines": {
+ "node": "20.x || 22.x || 23.x || 24.x || 25.x"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bl/node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -2478,6 +2616,30 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2575,6 +2737,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "license": "ISC"
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2615,6 +2783,32 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2702,7 +2896,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -2716,6 +2909,30 @@
}
}
},
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2763,7 +2980,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -2811,6 +3027,57 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.6.5",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz",
+ "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.4.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.18.3"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
+ "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.18.3",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
@@ -2825,6 +3092,12 @@
"node": ">=10.13.0"
}
},
+ "node_modules/err-code": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz",
+ "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==",
+ "license": "MIT"
+ },
"node_modules/es-abstract": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -3217,6 +3490,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3450,6 +3724,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3524,6 +3807,12 @@
"node": ">=16.0.0"
}
},
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3591,6 +3880,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3652,6 +3947,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-browser-rtc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
+ "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==",
+ "license": "MIT"
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -3722,6 +4023,12 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "license": "MIT"
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -3896,6 +4203,26 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3933,6 +4260,18 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -4883,6 +5222,39 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4900,17 +5272,21 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -4931,6 +5307,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "license": "MIT"
+ },
"node_modules/napi-postinstall": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@@ -4954,6 +5336,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
@@ -5035,6 +5426,30 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-abi": {
+ "version": "3.87.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
+ "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-abi/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -5046,7 +5461,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -5165,6 +5579,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5331,6 +5754,32 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5353,6 +5802,16 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/pump": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
+ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5367,7 +5826,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -5384,6 +5842,39 @@
],
"license": "MIT"
},
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
@@ -5414,6 +5905,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5554,6 +6059,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -5811,6 +6336,136 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/simple-peer": {
+ "version": "9.11.1",
+ "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz",
+ "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "debug": "^4.3.2",
+ "err-code": "^3.0.1",
+ "get-browser-rtc": "^1.1.0",
+ "queue-microtask": "^1.2.3",
+ "randombytes": "^2.1.0",
+ "readable-stream": "^3.6.0"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
+ "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.4.1",
+ "engine.io": "~6.6.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
+ "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.4.1",
+ "ws": "~8.18.3"
+ }
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
+ "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
+ "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5841,6 +6496,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string.prototype.includes": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@@ -6047,6 +6711,34 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6154,6 +6846,18 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6307,7 +7011,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/unrs-resolver": {
@@ -6386,6 +7089,34 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6501,6 +7232,41 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "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
+ }
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index ad163d4..3612463 100644
--- a/package.json
+++ b/package.json
@@ -3,21 +3,28 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "node server.js",
"build": "next build",
- "start": "next start",
+ "start": "NODE_ENV=production node server.js",
"lint": "eslint"
},
"dependencies": {
+ "better-sqlite3": "^12.6.2",
"next": "16.1.6",
"react": "19.2.3",
- "react-dom": "19.2.3"
+ "react-dom": "19.2.3",
+ "simple-peer": "^9.11.1",
+ "socket.io": "^4.8.3",
+ "socket.io-client": "^4.8.3",
+ "uuid": "^13.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
+ "@types/better-sqlite3": "^7.6.13",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "@types/uuid": "^10.0.0",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"tailwindcss": "^4",
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..fb7df81
--- /dev/null
+++ b/server.js
@@ -0,0 +1,227 @@
+const { createServer } = require('http');
+const { parse } = require('url');
+const next = require('next');
+const { Server } = require('socket.io');
+const Database = require('better-sqlite3');
+const path = require('path');
+
+const dev = process.env.NODE_ENV !== 'production';
+const hostname = 'localhost';
+const port = parseInt(process.env.PORT || '3000', 10);
+
+const app = next({ dev, hostname, port });
+const handle = app.getRequestHandler();
+
+// Connexion à la base de données SQLite
+const dbPath = path.join(process.cwd(), 'visio.db');
+const db = new Database(dbPath);
+
+// Fonction pour nettoyer les salles inactives dans la DB
+function cleanupInactiveRoomsInDB() {
+ const stmt = db.prepare(`
+ UPDATE rooms SET is_active = 0
+ WHERE is_active = 1
+ AND datetime(last_activity, '+5 minutes') < datetime('now')
+ `);
+ const result = stmt.run();
+ if (result.changes > 0) {
+ console.log(`Nettoyage: ${result.changes} salle(s) inactive(s) fermée(s)`);
+ }
+ return result.changes;
+}
+
+// Stockage des salles et participants en mémoire
+const rooms = new Map();
+const roomTimers = new Map();
+
+// Durée d'inactivité avant fermeture (5 minutes)
+const INACTIVITY_TIMEOUT = 5 * 60 * 1000;
+
+app.prepare().then(() => {
+ // Nettoyer les salles inactives au démarrage
+ console.log('Nettoyage des salles inactives au démarrage...');
+ cleanupInactiveRoomsInDB();
+
+ // Lancer un nettoyage périodique toutes les minutes
+ setInterval(() => {
+ cleanupInactiveRoomsInDB();
+ }, 60 * 1000);
+
+ const httpServer = createServer((req, res) => {
+ const parsedUrl = parse(req.url, true);
+ handle(req, res, parsedUrl);
+ });
+
+ const io = new Server(httpServer, {
+ cors: {
+ origin: '*',
+ methods: ['GET', 'POST']
+ }
+ });
+
+ // Fonction pour réinitialiser le timer d'inactivité
+ function resetRoomTimer(roomId) {
+ if (roomTimers.has(roomId)) {
+ clearTimeout(roomTimers.get(roomId));
+ }
+
+ const timer = setTimeout(() => {
+ const room = rooms.get(roomId);
+ if (room && room.participants.size === 0) {
+ console.log(`Fermeture de la salle ${roomId} pour inactivité`);
+ io.to(roomId).emit('room-closed');
+ rooms.delete(roomId);
+ roomTimers.delete(roomId);
+
+ // Marquer la salle comme fermée dans la DB
+ fetch(`http://localhost:${port}/api/rooms/${roomId}`, {
+ method: 'DELETE'
+ }).catch(console.error);
+ }
+ }, INACTIVITY_TIMEOUT);
+
+ roomTimers.set(roomId, timer);
+ }
+
+ io.on('connection', (socket) => {
+ console.log('Nouvelle connexion:', socket.id);
+
+ let currentRoom = null;
+ let currentUser = null;
+
+ socket.on('join-room', ({ roomId, userName }) => {
+ currentRoom = roomId;
+ currentUser = { id: socket.id, name: userName, videoOff: false, muted: false };
+
+ // Rejoindre la room Socket.io
+ socket.join(roomId);
+
+ // Initialiser ou récupérer la salle
+ if (!rooms.has(roomId)) {
+ rooms.set(roomId, {
+ participants: new Map()
+ });
+ }
+
+ const room = rooms.get(roomId);
+
+ // Envoyer la liste des participants existants au nouveau (avec leur état audio/vidéo)
+ const existingUsers = Array.from(room.participants.values());
+ socket.emit('existing-users', { users: existingUsers });
+
+ // Ajouter le nouveau participant
+ room.participants.set(socket.id, currentUser);
+
+ // Notifier les autres de la nouvelle connexion
+ socket.to(roomId).emit('user-joined', {
+ userId: socket.id,
+ userName: userName
+ });
+
+ // Réinitialiser le timer d'inactivité
+ resetRoomTimer(roomId);
+
+ console.log(`${userName} a rejoint la salle ${roomId}`);
+ });
+
+ socket.on('offer', ({ offer, to }) => {
+ const room = rooms.get(currentRoom);
+ const fromUser = room?.participants.get(socket.id);
+
+ socket.to(to).emit('offer', {
+ offer,
+ from: socket.id,
+ fromName: fromUser?.name || 'Inconnu'
+ });
+ });
+
+ socket.on('answer', ({ answer, to }) => {
+ socket.to(to).emit('answer', {
+ answer,
+ from: socket.id
+ });
+ });
+
+ socket.on('ice-candidate', ({ candidate, to }) => {
+ socket.to(to).emit('ice-candidate', {
+ candidate,
+ from: socket.id
+ });
+ });
+
+ socket.on('name-change', ({ name }) => {
+ if (currentRoom && currentUser) {
+ currentUser.name = name;
+ const room = rooms.get(currentRoom);
+ if (room) {
+ room.participants.set(socket.id, currentUser);
+ }
+
+ socket.to(currentRoom).emit('user-name-changed', {
+ userId: socket.id,
+ newName: name
+ });
+ }
+ });
+
+ socket.on('video-toggle', ({ videoOff }) => {
+ if (currentRoom && currentUser) {
+ currentUser.videoOff = videoOff;
+ const room = rooms.get(currentRoom);
+ if (room) {
+ room.participants.set(socket.id, currentUser);
+ }
+
+ socket.to(currentRoom).emit('user-video-toggle', {
+ userId: socket.id,
+ videoOff
+ });
+ }
+ });
+
+ socket.on('audio-toggle', ({ muted }) => {
+ if (currentRoom && currentUser) {
+ currentUser.muted = muted;
+ const room = rooms.get(currentRoom);
+ if (room) {
+ room.participants.set(socket.id, currentUser);
+ }
+
+ socket.to(currentRoom).emit('user-audio-toggle', {
+ userId: socket.id,
+ muted
+ });
+ }
+ });
+
+ // Répondre aux pings des clients pour la détection de déconnexion
+ socket.on('ping-server', () => {
+ socket.emit('pong-server');
+ });
+
+ socket.on('disconnect', () => {
+ if (currentRoom) {
+ const room = rooms.get(currentRoom);
+ if (room) {
+ room.participants.delete(socket.id);
+
+ // Notifier les autres
+ socket.to(currentRoom).emit('user-left', {
+ userId: socket.id
+ });
+
+ console.log(`Utilisateur ${socket.id} a quitté la salle ${currentRoom}`);
+
+ // Si la salle est vide, démarrer le timer de fermeture
+ if (room.participants.size === 0) {
+ resetRoomTimer(currentRoom);
+ }
+ }
+ }
+ });
+ });
+
+ httpServer.listen(port, () => {
+ console.log(`> Serveur prêt sur http://${hostname}:${port}`);
+ });
+});
diff --git a/src/app/api/rooms/[id]/route.ts b/src/app/api/rooms/[id]/route.ts
new file mode 100644
index 0000000..029735e
--- /dev/null
+++ b/src/app/api/rooms/[id]/route.ts
@@ -0,0 +1,67 @@
+import { NextResponse } from 'next/server';
+import { getRoom, updateRoomActivity, closeRoom } from '@/lib/database';
+
+export async function GET(
+ request: Request,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const { id } = await params;
+
+ const room = getRoom(id);
+
+ if (!room) {
+ return NextResponse.json(
+ { error: 'Salle non trouvée' },
+ { status: 404 }
+ );
+ }
+
+ if (!room.is_active) {
+ return NextResponse.json(
+ { error: 'Cette salle a été fermée' },
+ { status: 410 }
+ );
+ }
+
+ return NextResponse.json(room);
+}
+
+export async function PATCH(
+ request: Request,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const { id } = await params;
+
+ const room = getRoom(id);
+
+ if (!room) {
+ return NextResponse.json(
+ { error: 'Salle non trouvée' },
+ { status: 404 }
+ );
+ }
+
+ updateRoomActivity(id);
+
+ return NextResponse.json({ success: true });
+}
+
+export async function DELETE(
+ request: Request,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const { id } = await params;
+
+ const room = getRoom(id);
+
+ if (!room) {
+ return NextResponse.json(
+ { error: 'Salle non trouvée' },
+ { status: 404 }
+ );
+ }
+
+ closeRoom(id);
+
+ return NextResponse.json({ success: true });
+}
diff --git a/src/app/api/rooms/route.ts b/src/app/api/rooms/route.ts
new file mode 100644
index 0000000..2cc1016
--- /dev/null
+++ b/src/app/api/rooms/route.ts
@@ -0,0 +1,48 @@
+import { NextResponse } from 'next/server';
+import { createRoom, getRoom } from '@/lib/database';
+import { generateRoomId } from '@/lib/utils';
+
+export async function POST(request: Request) {
+ try {
+ const body = await request.json();
+ const { name } = body;
+
+ const roomId = generateRoomId();
+ const room = createRoom(roomId, name || 'Visioconférence');
+
+ return NextResponse.json({
+ id: room.id,
+ name: room.name,
+ created_at: room.created_at,
+ });
+ } catch (error) {
+ console.error('Erreur lors de la création de la salle:', error);
+ return NextResponse.json(
+ { error: 'Erreur lors de la création de la salle' },
+ { status: 500 }
+ );
+ }
+}
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const id = searchParams.get('id');
+
+ if (!id) {
+ return NextResponse.json(
+ { error: 'ID de salle requis' },
+ { status: 400 }
+ );
+ }
+
+ const room = getRoom(id);
+
+ if (!room) {
+ return NextResponse.json(
+ { error: 'Salle non trouvée' },
+ { status: 404 }
+ );
+ }
+
+ return NextResponse.json(room);
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..51d914d 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,26 +1,133 @@
@import "tailwindcss";
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --background: #f8fafc;
+ --foreground: #1e293b;
+ --primary: #3b82f6;
+ --primary-hover: #2563eb;
+ --secondary: #64748b;
+ --accent: #10b981;
+ --danger: #ef4444;
+ --card-bg: #ffffff;
+ --border: #e2e8f0;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
+ --color-primary: var(--primary);
+ --color-primary-hover: var(--primary-hover);
+ --color-secondary: var(--secondary);
+ --color-accent: var(--accent);
+ --color-danger: var(--danger);
+ --color-card-bg: var(--card-bg);
+ --color-border: var(--border);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
body {
background: var(--background);
color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ font-family: 'Inter', Arial, Helvetica, sans-serif;
+}
+
+/* Styles personnalisés */
+.btn-primary {
+ @apply bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-6 rounded-lg transition-all duration-200 shadow-md hover:shadow-lg;
+}
+
+.btn-secondary {
+ @apply bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-6 rounded-lg transition-all duration-200;
+}
+
+.btn-danger {
+ @apply bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-lg transition-all duration-200;
+}
+
+.btn-icon {
+ @apply p-3 rounded-full transition-all duration-200 flex items-center justify-center;
+}
+
+.card {
+ @apply bg-white rounded-xl shadow-md p-6 border border-gray-100;
+}
+
+.input-field {
+ @apply w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200;
+}
+
+/* Animation pour les vidéos */
+.video-container {
+ @apply relative rounded-xl overflow-hidden bg-gray-900 shadow-lg;
+ width: 100%;
+ max-width: 600px;
+ min-width: 280px;
+ min-height: 200px;
+ flex: 1 1 300px;
+}
+
+.video-container.aspect-video {
+ aspect-ratio: 16 / 9;
+}
+
+/* Bordure verte quand quelqu'un parle */
+.video-container.speaking {
+ box-shadow: 0 0 0 3px #22c55e, 0 0 20px rgba(34, 197, 94, 0.4);
+ transition: box-shadow 0.15s ease-in-out;
+}
+
+.video-container video {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.video-container .video-placeholder {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #1f2937;
+}
+
+.video-label {
+ @apply absolute bottom-3 left-3 bg-black/60 text-white px-3 py-1 rounded-full text-sm font-medium;
+}
+
+/* Flex responsive pour les vidéos */
+.video-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ justify-content: center;
+ align-items: flex-start;
+ max-width: 1800px;
+ margin: 0 auto;
+ padding: 1rem;
+}
+
+/* Ajustements selon le nombre de participants */
+.video-grid-1 .video-container {
+ max-width: 900px;
+ flex: 0 1 900px;
+}
+
+.video-grid-2 .video-container {
+ max-width: 700px;
+ flex: 0 1 700px;
+}
+
+@media (max-width: 768px) {
+ .video-container {
+ flex: 1 1 100%;
+ max-width: 100%;
+ }
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..56c53e1 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -13,8 +13,18 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Visio - Vidéoconférence Simple",
+ description: "Créez et rejoignez des visioconférences gratuitement, sans inscription",
+ keywords: ["visioconférence", "vidéo", "appel", "gratuit", "sans inscription"],
+ authors: [{ name: "Arthur P" }],
+ openGraph: {
+ title: "Visio - Vidéoconférence Simple",
+ description: "Créez et rejoignez des visioconférences gratuitement, sans inscription",
+ url: "https://visio.arthurp.fr",
+ siteName: "Visio",
+ locale: "fr_FR",
+ type: "website",
+ },
};
export default function RootLayout({
@@ -23,9 +33,9 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 295f8fd..7ce1b6c 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,65 +1,195 @@
-import Image from "next/image";
+'use client';
+
+import Link from 'next/link';
+import { useState } from 'react';
+import { useRouter } from 'next/navigation';
export default function Home() {
+ const [roomCode, setRoomCode] = useState('');
+ const [isCreating, setIsCreating] = useState(false);
+ const router = useRouter();
+
+ const handleCreateRoom = async () => {
+ setIsCreating(true);
+ try {
+ const response = await fetch('/api/rooms', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ name: 'Nouvelle visioconférence' }),
+ });
+
+ const data = await response.json();
+ if (data.id) {
+ router.push(`/room/${data.id}`);
+ }
+ } catch (error) {
+ console.error('Erreur lors de la création de la salle:', error);
+ alert('Erreur lors de la création de la salle');
+ } finally {
+ setIsCreating(false);
+ }
+ };
+
+ const handleJoinRoom = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (roomCode.trim()) {
+ router.push(`/room/${roomCode.trim()}`);
+ }
+ };
+
return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
+ {/* Header */}
+
+
+
+
+
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
+
+
+
+
+
+
+ {/* Hero Section */}
+
+
+
+ Visioconférence simple et gratuite
+
+
+ Créez une salle de visioconférence en un clic et partagez le lien avec vos participants.
+ Aucune inscription requise.
-
-
-
- Deploy Now
-
-
- Documentation
-
+
+ {/* Actions Cards */}
+
+ {/* Créer une visio */}
+
+
+
+
Créer une visio
+
+ Lancez une nouvelle visioconférence et invitez des participants
+
+
+
+
+
+ {/* Rejoindre une visio */}
+
+
+
+
Rejoindre une visio
+
+ Entrez le code de la salle pour rejoindre une visioconférence
+
+
+
+
-
-
+
+ {/* Features */}
+
+
+ Pourquoi utiliser Visio ?
+
+
+
+
+
Rapide
+
Créez une visio en un seul clic, sans attente
+
+
+
+
Sans inscription
+
Aucun compte requis pour utiliser le service
+
+
+
+
Facile à partager
+
Partagez simplement le lien avec vos participants
+
+
+
+
+
+ {/* Footer */}
+
+
);
}
diff --git a/src/app/room/[id]/page.tsx b/src/app/room/[id]/page.tsx
new file mode 100644
index 0000000..5befb13
--- /dev/null
+++ b/src/app/room/[id]/page.tsx
@@ -0,0 +1,951 @@
+'use client';
+
+import { useState, useEffect, useRef, useCallback } from 'react';
+import { useParams, useRouter } from 'next/navigation';
+import Link from 'next/link';
+import { io, Socket } from 'socket.io-client';
+import { useLocalStorage } from '@/hooks/useLocalStorage';
+
+interface Participant {
+ id: string;
+ name: string;
+ stream?: MediaStream;
+ videoOff?: boolean;
+ muted?: boolean;
+}
+
+export default function RoomPage() {
+ const params = useParams();
+ const router = useRouter();
+ const roomId = params.id as string;
+
+ const [storedUserName, setStoredUserName] = useLocalStorage('visio_username', '');
+ const [userName, setUserName] = useState('');
+ const [tempName, setTempName] = useState('');
+ const [isJoined, setIsJoined] = useState(false);
+ const [roomExists, setRoomExists] = useState(null);
+ const [roomClosed, setRoomClosed] = useState(false);
+ const [participants, setParticipants] = useState([]);
+ const [isMuted, setIsMuted] = useState(false);
+ const [isVideoOff, setIsVideoOff] = useState(false);
+ const [showSettings, setShowSettings] = useState(false);
+ const [copied, setCopied] = useState(false);
+ const [error, setError] = useState(null);
+ const [speakingUsers, setSpeakingUsers] = useState>(new Set());
+
+ const localVideoRef = useRef(null);
+ const audioContextRef = useRef(null);
+ const analyzersRef = useRef