{"_id":"@bytepulse/pulsewave-client","_rev":"10-c8819e8b3e0638c1d73b9a9323d42573","name":"@bytepulse/pulsewave-client","dist-tags":{"latest":"0.3.8"},"versions":{"0.2.0":{"name":"@bytepulse/pulsewave-client","version":"0.2.0","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.2.0","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"ea778db8f7d8b129b12ab5c3ef001a0091356a4c","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.2.0.tgz","fileCount":246,"integrity":"sha512-sl0z0/hkkdJ1c/R+QIxfAnjBybdKbqmKkHzH6BXxtJoxtM7Lrk3ViSYLr19g+Hmf13tqnQJtsmzHulZpxzyJ3A==","signatures":[{"sig":"MEUCIQCuACYlpO6rYe+3wI1+47OM/SjxhV/QtuiGZen3KN08UQIgC2oP1ANJQwQKohhjAY6aPeF2xv6JWZBSd/smQFXZ4tE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":362210},"main":"./dist/index.js","_from":"file:bytepulse-pulsewave-client-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"_resolved":"/private/var/folders/kl/sfz43qn90619crk1pc8lx2900000gn/T/3df8b3f20805fef5dca83cbf44dbc788/bytepulse-pulsewave-client-0.2.0.tgz","_integrity":"sha512-sl0z0/hkkdJ1c/R+QIxfAnjBybdKbqmKkHzH6BXxtJoxtM7Lrk3ViSYLr19g+Hmf13tqnQJtsmzHulZpxzyJ3A==","repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^18.0.0","react-dom":"^18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.2.0_1767560545889_0.540802306048676","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@bytepulse/pulsewave-client","version":"0.3.0","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.0","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"4bb9be6406b5d4c288e40ff8cf48cd82dea7c03a","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.0.tgz","fileCount":2,"integrity":"sha512-gVvO2yJux6F3puAqs/Rpaf8fnngF15IQsIkKzM6chOR4HmSBCDVfBw7eujcmY1xlJ3KkMJAl0f6KdhYKNRXYfg==","signatures":[{"sig":"MEQCIGCD/TCHbnt2Iy3RqbDOnhMH5zDGaFl8VSQTDcSyBvf6AiAtg5KBD6WL9BAA3joT6KVKU0o+y37zvGWWj0q7WqUj2w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":9814},"main":"./dist/index.js","_from":"file:bytepulse-pulsewave-client-0.3.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"_resolved":"/private/var/folders/kl/sfz43qn90619crk1pc8lx2900000gn/T/b7bbbb4c050b609e9ab97ccffce96281/bytepulse-pulsewave-client-0.3.0.tgz","_integrity":"sha512-gVvO2yJux6F3puAqs/Rpaf8fnngF15IQsIkKzM6chOR4HmSBCDVfBw7eujcmY1xlJ3KkMJAl0f6KdhYKNRXYfg==","repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.0_1767561553311_0.5159286137208321","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@bytepulse/pulsewave-client","version":"0.3.1","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.1","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"0dd1b7d048336db5c9d7eb480fea41c83f7f00e5","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.1.tgz","fileCount":246,"integrity":"sha512-hYotRBpzmZoJv88V6++7Lu6BdN0Bj0uK0XJ2ZYeqFSSPE0+w0ojAqBMFbEHbeXrhxqsad8pZQRWJhieWkUj4+Q==","signatures":[{"sig":"MEYCIQC9SHoGBJaHhnZZp87Ea0zvZu36T5uZtcUnGFR0wHNWzwIhAMmZwinw3PIOlfgSSoa+VxpRmwryivkSJgPfDTuusJC6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":362210},"main":"./dist/index.js","_from":"file:bytepulse-pulsewave-client-0.3.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"_resolved":"/private/var/folders/kl/sfz43qn90619crk1pc8lx2900000gn/T/44482ed533bb180f8587de74542c7392/bytepulse-pulsewave-client-0.3.1.tgz","_integrity":"sha512-hYotRBpzmZoJv88V6++7Lu6BdN0Bj0uK0XJ2ZYeqFSSPE0+w0ojAqBMFbEHbeXrhxqsad8pZQRWJhieWkUj4+Q==","repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.1_1767562564620_0.9972192656523544","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@bytepulse/pulsewave-client","version":"0.3.2","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.2","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"9c6d5fca41c5da3c88eb946aa9dd0d42f7a1705a","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.2.tgz","fileCount":252,"integrity":"sha512-YQRLgZn+B8g60aj8U8G+BmiEL3N1MK0fZDlBzMzAPoKpmhRbOlwjyxKcal0G4u2WhRnQlTSDt4/SNHId1VE3Aw==","signatures":[{"sig":"MEQCIHr66HPxngG4iVPiRvcgSDVUSpSNVnebdzR7v5nrfEtiAiBkucvtTZtW4fJQar/vH8poVN1xBNw3DXVaGwAbYuTSQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":425417},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"},"./components/styles.css":"./dist/components/styles.css"},"gitHead":"580d9192db0b8926ad11de5bc469b56234b72bc3","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"workspace:*"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.2_1767884209455_0.6051055800914391","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@bytepulse/pulsewave-client","version":"0.3.3","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.3","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"5beddb2669f5565df76dff60b1b523d8a4f8dde8","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.3.tgz","fileCount":252,"integrity":"sha512-HT6XUpY61WUK87Nnfv2L1B8jiOzVCTiqUM7VhlPsg/goMaK1Cx/InOlLOtk51UoAikczA84wquw05GwUKixr4g==","signatures":[{"sig":"MEUCIDdxnZ/AleV1SsSFK4YQYaK3vLT5zxAadqAs24x/kpvPAiEA6cWypTqCtbhDdJjDMJYhD3VjcvaAOwZz3QxxZHHjLdw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":425412},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"},"./components/styles.css":"./dist/components/styles.css"},"gitHead":"251217c35955383eec68008231430ecf5e797636","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"^0.2.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.3_1767885510695_0.3358989000892414","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@bytepulse/pulsewave-client","version":"0.3.4","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.4","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"63dd095ce173e558065ab0eb5b38565ea26582b8","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.4.tgz","fileCount":252,"integrity":"sha512-m2UhvTubN1QkSWa1Q1/Pi9i9l3Tf1YCnDX10vSw5rvSUHwzeiw155efo+D0wNtFZdzNOCHJ/3xwN7XTiBvumrA==","signatures":[{"sig":"MEYCIQD2nnEEFjoqFx9PfD6eTBNeINIomKpBS/C0l1kknJ8gGwIhAN/T4xG+a7KmN4dskTsraqvmEmwTggB2lBblSK5f80w2","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":427227},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"},"./components/styles.css":"./dist/components/styles.css"},"gitHead":"bbad803f0959d8830438185ce2782d54aaa941a0","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"^0.2.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.4_1767886416924_0.5182690682360778","host":"s3://npm-registry-packages-npm-production"}},"0.3.5":{"name":"@bytepulse/pulsewave-client","version":"0.3.5","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.5","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"a3ecf209d98360afb557e0eae486991cf572cf4a","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.5.tgz","fileCount":252,"integrity":"sha512-Bd6Ay+awOvM0X+NeepIHXqS/DZ1L4tEFl2j3LBb1fli03HfPJPa/7/HURhUbg4LdB5FEpRuwpGJFRXwJ1gOiww==","signatures":[{"sig":"MEUCIGk4Gpaeog9Dc0H3zMpVEiEhtJJjeBxTQopWnEwew9StAiEAt+O19grGc/fOJ4yqYQtDBY59MXsn8hiSo0GTxTL1bsg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":426595},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"},"./components/styles.css":"./dist/components/styles.css"},"gitHead":"429dd7da20e8eab2ea84441d74490cd9e3c86267","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"^0.2.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.5_1767965612570_0.9293118822744757","host":"s3://npm-registry-packages-npm-production"}},"0.3.6":{"name":"@bytepulse/pulsewave-client","version":"0.3.6","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.6","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"b81a82c10d3b7b3d52045009f74a47500c61ce0b","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.6.tgz","fileCount":276,"integrity":"sha512-U4NUDlWWYqcWsVmLjsFDZ6wHrD7m1QWz/1VwYTQpPD8j6QZdkBDbHqd5WqGpD37S3S4Q1JTFZr0114ri0KqIVw==","signatures":[{"sig":"MEQCIF/vMtvcWxrYxfKVWfikYG56XF3ounc8SjzphbeU5ZaSAiAoDSeYJVCleFZ0kw3l9YfZVOU7kcnW4FhjxoBos8UWmA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":505793},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"},"./components":{"types":"./dist/components/index.d.ts","import":"./dist/components/index.js"},"./components/styles.css":"./dist/components/styles.css"},"gitHead":"f7d8182f1e9860b6136908145b41a2db767bdc08","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"^0.2.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.6_1768032888435_0.5550904414327429","host":"s3://npm-registry-packages-npm-production"}},"0.3.7":{"name":"@bytepulse/pulsewave-client","version":"0.3.7","author":{"url":"https://github.com/kaushald4","name":"Kaushal Mehta"},"license":"MIT","_id":"@bytepulse/pulsewave-client@0.3.7","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"dist":{"shasum":"b3abbfa369faf0e48879711d8e2ec4731a619ebf","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.7.tgz","fileCount":2,"integrity":"sha512-mDQYdQxKEnkezMI3BFOEaTBhJJw9Uk6jvZU/Y8zp+WHq51GWL0bwFGwk23AZH7OLFbF3pt5Nas9Dx1TnUimCIA==","signatures":[{"sig":"MEQCIF+5iKJmxhD4ZOv+YjtPvvGKdc1KIdDhDHn3l7BUG+ePAiA3Lrp32zRXQNkupHdZv+eW5VQlfJvQucScdfiEfXgOaA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31185},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"}},"gitHead":"4803fc4389eb5fd325f3aa068ac0311c63fa6d58","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest","build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"repository":{"url":"git+https://github.com/bytepulseflow/pulsewave.git","type":"git","directory":"packages/client"},"_npmVersion":"10.9.3","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","directories":{},"_nodeVersion":"22.18.0","dependencies":{"mediasoup-client":"^3.7.10","@bytepulse/pulsewave-shared":"^0.2.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^24.0.0","react":"^18.2.0","eslint":"^8.56.0","vitest":"^1.2.0","react-dom":"^18.2.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","eslint-plugin-react":"^7.33.2","@testing-library/react":"^14.1.2","@testing-library/jest-dom":"^6.1.5","@typescript-eslint/parser":"^6.19.0","eslint-plugin-react-hooks":"^4.6.0","@typescript-eslint/eslint-plugin":"^6.19.0"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/pulsewave-client_0.3.7_1768149830592_0.9376761179582533","host":"s3://npm-registry-packages-npm-production"}},"0.3.8":{"name":"@bytepulse/pulsewave-client","version":"0.3.8","description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./ui":{"types":"./dist/ui/index.d.ts","import":"./dist/ui/index.js"}},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/bytepulseflow/pulsewave.git","directory":"packages/client"},"homepage":"https://github.com/bytepulseflow/pulsewave#readme","bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"license":"MIT","scripts":{"build":"tsc && cp -r src/components/styles.css dist/components/ && cp -r src/ui dist/","dev":"tsc --watch","clean":"rm -rf dist","lint":"eslint src --ext .ts,.tsx","type-check":"tsc --noEmit","test":"vitest"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"dependencies":{"@bytepulse/pulsewave-shared":"^0.2.4","mediasoup-client":"^3.7.10"},"devDependencies":{"@testing-library/jest-dom":"^6.1.5","@testing-library/react":"^14.1.2","@types/react":"^18.2.48","@types/react-dom":"^18.2.18","@typescript-eslint/eslint-plugin":"^6.19.0","@typescript-eslint/parser":"^6.19.0","@vitest/ui":"^1.2.0","eslint":"^8.56.0","eslint-plugin-react":"^7.33.2","eslint-plugin-react-hooks":"^4.6.0","jsdom":"^24.0.0","react":"^18.2.0","react-dom":"^18.2.0","typescript":"^5.3.3","vitest":"^1.2.0"},"author":{"name":"Kaushal Mehta","url":"https://github.com/kaushald4"},"_id":"@bytepulse/pulsewave-client@0.3.8","gitHead":"4803fc4389eb5fd325f3aa068ac0311c63fa6d58","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-25gMf70tVFncBTZfk4JnpSj0OiN7If3kyO7po55IMHoPJKmtuVHtbtD0k5oXN+d63GlcQa09L5N33IkMqXfcyQ==","shasum":"33575ad17f0094b4a58c533ea74d6143f63d6d42","tarball":"https://registry.npmjs.org/@bytepulse/pulsewave-client/-/pulsewave-client-0.3.8.tgz","fileCount":284,"unpackedSize":515230,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC3KwEDjIr9ZlvGoz90DmKl3EFXqTJNGg11QWPFfOZOsQIhAI4Fb74laROM6NEw5H2Br52qrKzaJUsa/PVpaI3olFnU"}]},"_npmUser":{"name":"kaushald4","email":"kaushalkumar304@gmail.com"},"directories":{},"maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pulsewave-client_0.3.8_1768149950883_0.38183156357834247"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-04T21:02:25.756Z","modified":"2026-01-11T16:45:51.260Z","0.2.0":"2026-01-04T21:02:26.024Z","0.3.0":"2026-01-04T21:19:13.453Z","0.3.1":"2026-01-04T21:36:04.749Z","0.3.2":"2026-01-08T14:56:49.595Z","0.3.3":"2026-01-08T15:18:30.873Z","0.3.4":"2026-01-08T15:33:37.082Z","0.3.5":"2026-01-09T13:33:32.742Z","0.3.6":"2026-01-10T08:14:48.641Z","0.3.7":"2026-01-11T16:43:50.781Z","0.3.8":"2026-01-11T16:45:51.070Z"},"bugs":{"url":"https://github.com/bytepulseflow/pulsewave/issues"},"author":{"name":"Kaushal Mehta","url":"https://github.com/kaushald4"},"license":"MIT","homepage":"https://github.com/bytepulseflow/pulsewave#readme","repository":{"type":"git","url":"git+https://github.com/bytepulseflow/pulsewave.git","directory":"packages/client"},"description":"PulseWave Client SDK - React hooks and components for WebRTC video/audio","maintainers":[{"name":"kaushald4","email":"kaushalkumar304@gmail.com"}],"readme":"# @bytepulse/pulsewave-client\n\n> **⚠️ WARNING: This project is still under active development and testing. Do not use in production environments.**\n\nReact client SDK for PulseWave. Provides simple hooks and components for building video/audio conferencing applications.\n\n## Installation\n\n```bash\npnpm install @bytepulse/pulsewave-client\n```\n\n## Server Setup\n\nThe PulseWave client SDK requires a running PulseWave server. You can set up the server in two ways:\n\n### Option 1: Using Docker (Recommended)\n\n> **⚠️ NOTE: Before running the server set `ANNOUNCED_IP={host_ip_address}` in .env file to your machine ipv4 address.**\n\n```bash\n# Clone the repository\ngit clone https://github.com/bytepulseflow/pulsewave.git\ncd pulsewave/server\n\n# Copy environment file\ncp .env.example .env\n\n# Start the server with docker-compose\ndocker-compose up -d\n```\n\nThe server will be available at `http://localhost:3000`\n\n### Option 2: Manual Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/bytepulseflow/pulsewave.git\ncd pulsewave\n\n# Install dependencies\ncd packages/server\npnpm install\n\n# Copy environment file\ncp .env.example .env\n\n# Edit .env with your configuration\n# Required: API_KEY and API_SECRET for token generation\n\n# Start the server\npnpm start:dev\n```\n\nFor more details on server, visit the [PulseWave Server GitHub Repository](https://github.com/bytepulseflow/pulsewave/tree/main/packages/server).\n\n## Quick Start\n\nPulseWave Client SDK provides two approaches for building video/audio applications:\n\n### Option 1: Built-in Component-Based Usage\n\nUse the pre-built components for quick setup with minimal code:\n\n```tsx\nimport { useState } from 'react';\nimport {\n  RoomProvider,\n  useRoom,\n  useLocalParticipant,\n  useParticipants,\n  useConnectionState,\n  PulseParticipantView,\n} from '@bytepulse/pulsewave-client';\nimport { DataProviderType } from '@bytepulse/pulsewave-shared';\n\nfunction VideoRoom() {\n  const { disconnect, localTracks } = useRoom();\n  const localParticipant = useLocalParticipant();\n  const participants = useParticipants();\n  const connectionState = useConnectionState();\n\n  const toggleCamera = async () => {\n    if (localParticipant) {\n      const hasVideo = localParticipant.getTracks().some((t) => t.kind === 'video');\n      if (hasVideo) {\n        await localParticipant.disableCamera();\n      } else {\n        await localParticipant.enableCamera();\n      }\n    }\n  };\n\n  const toggleMicrophone = async () => {\n    if (localParticipant) {\n      const audioTrack = localParticipant.getTracks().find((t) => t.kind === 'audio');\n      if (audioTrack) {\n        if (audioTrack.track?.isMuted) {\n          await localParticipant.unmuteAudio();\n        } else {\n          await localParticipant.muteAudio();\n        }\n      } else {\n        await localParticipant.enableMicrophone();\n      }\n    }\n  };\n\n  return (\n    <div className=\"video-room w-full bg-slate-950 h-screen pt-8\">\n      <div className=\"relative\">\n        {connectionState === 'connected' && (\n          <div className=\"participants\">\n            {/* Local Participant */}\n            {localParticipant && (\n              <div className=\" mx-auto w-[40%]\">\n                <PulseParticipantView\n                  key={`local-${localTracks.length}`}\n                  objectFit=\"fill\"\n                  className=\"\"\n                  participant={localParticipant}\n                />\n              </div>\n            )}\n\n            {/* Remote Participants */}\n            {participants.map((participant) => {\n              return (\n                <div key={participant.sid} className=\"w-1/4\">\n                  <PulseParticipantView objectFit=\"fill\" participant={participant} />\n                </div>\n              );\n            })}\n          </div>\n        )}\n      </div>\n      <div className=\"controls absolute w-full flex justify-center items-center mx-auto bottom-14 z-10\">\n        <button\n          className=\"bg-slate-800 text-white py-2 px-4 rounded-2xl\"\n          onClick={disconnect}\n          disabled={connectionState === 'disconnected'}\n        >\n          Leave Room\n        </button>\n        <button className=\"bg-slate-800 text-white py-2 px-4 rounded-2xl\" onClick={toggleCamera}>\n          Toggle Camera\n        </button>\n        <button\n          className=\"bg-slate-800 text-white py-2 px-4 rounded-2xl\"\n          onClick={toggleMicrophone}\n        >\n          Toggle Mic\n        </button>\n      </div>\n    </div>\n  );\n}\n\nexport default function App() {\n  const [username, setUsername] = useState('');\n  const [roomName, setRoomName] = useState('');\n  const [token, setToken] = useState('');\n  const [isJoined, setIsJoined] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState('');\n\n  // Function to generate token from API\n  const generateToken = async (identity: string, room: string, name: string) => {\n    try {\n      const response = await fetch('http://localhost:3000/api/token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          identity,\n          name,\n          room,\n          canPublish: true,\n          canSubscribe: true,\n          canPublishData: true,\n        }),\n      });\n\n      if (!response.ok) {\n        throw new Error('Failed to generate token');\n      }\n\n      const data = await response.json();\n      return data.token;\n    } catch (err) {\n      throw new Error('Failed to generate access token. Please ensure the server is running.');\n    }\n  };\n\n  const handleJoin = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (!username || !roomName) {\n      return;\n    }\n\n    setIsLoading(true);\n    setError('');\n\n    try {\n      const generatedToken = await generateToken(username, roomName, username);\n      setToken(generatedToken);\n      setIsJoined(true);\n    } catch (err) {\n      setError(err instanceof Error ? err.message : 'An error occurred');\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleLeave = () => {\n    setIsJoined(false);\n    setToken('');\n    setError('');\n  };\n\n  return (\n    <div className=\"h-full\">\n      {!isJoined ? (\n        <form\n          onSubmit={handleJoin}\n          className=\"flex items-center max-w-80 mx-auto justify-center flex-col space-y-4 h-screen\"\n        >\n          <h2>Join a Room</h2>\n          {error && <div>{error}</div>}\n          <div className=\"w-full\">\n            <label className=\"block\" htmlFor=\"username\">\n              Username\n            </label>\n            <input\n              id=\"username\"\n              type=\"text\"\n              value={username}\n              onChange={(e) => setUsername(e.target.value)}\n              placeholder=\"Enter your name\"\n              required\n              className=\"border rounded-sm p-1 w-full\"\n              disabled={isLoading}\n            />\n          </div>\n          <div className=\"w-full\">\n            <label className=\"block\" htmlFor=\"roomName\">\n              Room Name\n            </label>\n            <input\n              id=\"roomName\"\n              type=\"text\"\n              value={roomName}\n              onChange={(e) => setRoomName(e.target.value)}\n              placeholder=\"Enter room name\"\n              required\n              className=\"border rounded-sm p-1 w-full\"\n              disabled={isLoading}\n            />\n          </div>\n          <button\n            className=\"bg-slate-900 px-4 py-2 text-white rounded-xl w-full\"\n            type=\"submit\"\n            disabled={isLoading}\n          >\n            {isLoading ? 'Generating Token...' : 'Join'}\n          </button>\n        </form>\n      ) : (\n        <RoomProvider\n          autoConnect\n          options={{\n            url: 'ws://localhost:3000',\n            room: roomName,\n            token: token,\n            dataProvider: {\n              type: DataProviderType.WebRTC,\n            },\n          }}\n        >\n          <VideoRoom />\n          <button onClick={handleLeave}>Leave Room</button>\n        </RoomProvider>\n      )}\n    </div>\n  );\n}\n```\n\n### Option 2: Hooks-Based API Usage\n\nBuild custom UI using hooks for full control:\n\n```tsx\nimport { useEffect, useState } from 'react';\nimport {\n  RoomProvider,\n  useRoom,\n  useLocalParticipant,\n  useParticipants,\n  useConnectionState,\n} from '@bytepulse/pulsewave-client';\nimport { VideoTrack, AudioTrack } from '@bytepulse/pulsewave-client/ui';\n\nfunction VideoRoom() {\n  const { disconnect } = useRoom();\n  const localParticipant = useLocalParticipant();\n  const participants = useParticipants();\n  const connectionState = useConnectionState();\n\n  useEffect(() => {\n    return () => {\n      disconnect();\n    };\n  }, []);\n\n  const toggleCamera = async () => {\n    if (localParticipant) {\n      const hasVideo = localParticipant.getTracks().some((t) => t.kind === 'video');\n      if (hasVideo) {\n        await localParticipant.disableCamera();\n      } else {\n        await localParticipant.enableCamera();\n      }\n    }\n  };\n\n  const toggleMicrophone = async () => {\n    if (localParticipant) {\n      const hasAudio = localParticipant.getTracks().some((t) => t.kind === 'audio');\n      if (hasAudio) {\n        await localParticipant.disableMicrophone();\n      } else {\n        await localParticipant.enableMicrophone();\n      }\n    }\n  };\n\n  return (\n    <div className=\"video-room\">\n      <div className=\"controls\">\n        <button onClick={disconnect} disabled={connectionState === 'disconnected'}>\n          Leave Room\n        </button>\n        <button onClick={toggleCamera}>Toggle Camera</button>\n        <button onClick={toggleMicrophone}>Toggle Mic</button>\n      </div>\n\n      {connectionState === 'connected' && (\n        <div className=\"participants\">\n          {/* Local Participant */}\n          {localParticipant && (\n            <div className=\"participant local\">\n              <h3>You ({localParticipant.name})</h3>\n              {localParticipant.getTracks().map((publication) => (\n                <div key={publication.sid}>\n                  {publication.track && publication.kind === 'video' && (\n                    <VideoTrack track={publication.track} muted={true} />\n                  )}\n                  {publication.track && publication.kind === 'audio' && (\n                    <AudioTrack track={publication.track} />\n                  )}\n                </div>\n              ))}\n            </div>\n          )}\n\n          {/* Remote Participants */}\n          {participants.map((participant) => (\n            <div key={participant.identity} className=\"participant\">\n              <h3>{participant.name}</h3>\n              {participant.getTracks().map((publication) => (\n                <div key={publication.sid}>\n                  {publication.track && publication.kind === 'video' && (\n                    <VideoTrack track={publication.track} />\n                  )}\n                  {publication.track && publication.kind === 'audio' && (\n                    <AudioTrack track={publication.track} />\n                  )}\n                </div>\n              ))}\n            </div>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default function App() {\n  const [username, setUsername] = useState('');\n  const [roomName, setRoomName] = useState('');\n  const [token, setToken] = useState('');\n  const [isJoined, setIsJoined] = useState(false);\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState('');\n\n  // Function to generate token from API\n  const generateToken = async (identity: string, room: string, name: string) => {\n    try {\n      const response = await fetch('http://localhost:3000/api/token', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          identity,\n          name,\n          room,\n          canPublish: true,\n          canSubscribe: true,\n          canPublishData: true,\n        }),\n      });\n\n      if (!response.ok) {\n        throw new Error('Failed to generate token');\n      }\n\n      const data = await response.json();\n      return data.token;\n    } catch (err) {\n      throw new Error('Failed to generate access token. Please ensure the server is running.');\n    }\n  };\n\n  const handleJoin = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (!username || !roomName) {\n      return;\n    }\n\n    setIsLoading(true);\n    setError('');\n\n    try {\n      const generatedToken = await generateToken(username, roomName, username);\n      setToken(generatedToken);\n      setIsJoined(true);\n    } catch (err) {\n      setError(err instanceof Error ? err.message : 'An error occurred');\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  const handleLeave = () => {\n    setIsJoined(false);\n    setToken('');\n    setError('');\n  };\n\n  return (\n    <div>\n      {!isJoined ? (\n        <form onSubmit={handleJoin} className=\"join-form\">\n          <h2>Join a Room</h2>\n          {error && <div className=\"error\">{error}</div>}\n          <div>\n            <label htmlFor=\"username\">Username:</label>\n            <input\n              id=\"username\"\n              type=\"text\"\n              value={username}\n              onChange={(e) => setUsername(e.target.value)}\n              placeholder=\"Enter your name\"\n              required\n              disabled={isLoading}\n            />\n          </div>\n          <div>\n            <label htmlFor=\"roomName\">Room Name:</label>\n            <input\n              id=\"roomName\"\n              type=\"text\"\n              value={roomName}\n              onChange={(e) => setRoomName(e.target.value)}\n              placeholder=\"Enter room name\"\n              required\n              disabled={isLoading}\n            />\n          </div>\n          <button type=\"submit\" disabled={isLoading}>\n            {isLoading ? 'Generating Token...' : 'Join'}\n          </button>\n        </form>\n      ) : (\n        <RoomProvider\n          autoConnect\n          options={{\n            url: 'ws://localhost:3000',\n            room: roomName,\n            token: token,\n          }}\n        >\n          <VideoRoom />\n          <button onClick={handleLeave}>Leave Room</button>\n        </RoomProvider>\n      )}\n    </div>\n  );\n}\n```\n\n### Option 3: Manual Track Attachment\n\nFor complete control, manually attach tracks to video elements:\n\n```tsx\nimport { useState, useRef, useEffect } from 'react';\nimport {\n  RoomProvider,\n  useRoom,\n  useLocalParticipant,\n  useParticipants,\n  useConnectionState,\n} from '@bytepulse/pulsewave-client';\nimport { DataProviderType } from '@bytepulse/pulsewave-shared';\n\nfunction ManualVideoRoom() {\n  const { disconnect } = useRoom();\n  const localParticipant = useLocalParticipant();\n  const participants = useParticipants();\n  const connectionState = useConnectionState();\n\n  // Refs for video elements\n  const localVideoRef = useRef<HTMLVideoElement>(null);\n  const remoteVideoRefs = useRef<Map<string, HTMLVideoElement>>(new Map());\n\n  // State for camera/mic status\n  const [isCameraEnabled, setIsCameraEnabled] = useState(false);\n  const [isMicEnabled, setIsMicEnabled] = useState(false);\n\n  // Toggle camera\n  const toggleCamera = async () => {\n    if (!localParticipant) return;\n\n    if (isCameraEnabled) {\n      await localParticipant.disableCamera();\n      setIsCameraEnabled(false);\n    } else {\n      await localParticipant.enableCamera();\n      setIsCameraEnabled(true);\n    }\n  };\n\n  // Toggle microphone\n  const toggleMicrophone = async () => {\n    if (!localParticipant) return;\n\n    if (isMicEnabled) {\n      await localParticipant.disableMicrophone();\n      setIsMicEnabled(false);\n    } else {\n      await localParticipant.enableMicrophone();\n      setIsMicEnabled(true);\n    }\n  };\n\n  // Attach local video track to video element\n  useEffect(() => {\n    if (!localParticipant || !localVideoRef.current) return;\n\n    const tracks = localParticipant.getTracks();\n    const videoTrack = tracks.find((t) => t.kind === 'video');\n\n    if (videoTrack?.track?.mediaTrack) {\n      const stream = new MediaStream([videoTrack.track.mediaTrack]);\n      localVideoRef.current.srcObject = stream;\n      localVideoRef.current.muted = true; // Mute local video to prevent feedback\n      localVideoRef.current.play().catch(console.error);\n    } else {\n      localVideoRef.current.srcObject = null;\n    }\n  }, [localParticipant, isCameraEnabled]);\n\n  // Attach local audio track to audio element\n  useEffect(() => {\n    if (!localParticipant || !localAudioRef.current) return;\n\n    const tracks = localParticipant.getTracks();\n    const audioTrack = tracks.find((t) => t.kind === 'audio');\n\n    if (audioTrack?.track?.mediaTrack) {\n      const stream = new MediaStream([audioTrack.track.mediaTrack]);\n      localAudioRef.current.srcObject = stream;\n      localAudioRef.current.muted = true; // Mute local audio to prevent feedback\n      localAudioRef.current.play().catch(console.error);\n    } else {\n      localAudioRef.current.srcObject = null;\n    }\n  }, [localParticipant, isMicEnabled]);\n\n  // Attach remote video tracks\n  useEffect(() => {\n    participants.forEach((participant) => {\n      const tracks = participant.getTracks();\n      const videoTrack = tracks.find((t) => t.kind === 'video');\n\n      const videoElement = remoteVideoRefs.current.get(participant.sid);\n      if (videoElement && videoTrack?.track?.mediaTrack) {\n        const stream = new MediaStream([videoTrack.track.mediaTrack]);\n        videoElement.srcObject = stream;\n        videoElement.play().catch(console.error);\n      }\n    });\n  }, [participants]);\n\n  // Attach remote audio tracks\n  useEffect(() => {\n    participants.forEach((participant) => {\n      const tracks = participant.getTracks();\n      const audioTrack = tracks.find((t) => t.kind === 'audio');\n\n      const audioElement = remoteAudioRefs.current.get(participant.sid);\n      if (audioElement && audioTrack?.track?.mediaTrack) {\n        const stream = new MediaStream([audioTrack.track.mediaTrack]);\n        audioElement.srcObject = stream;\n        audioElement.play().catch(console.error);\n      }\n    });\n  }, [participants]);\n\n  return (\n    <div className=\"video-room\">\n      <div className=\"controls\">\n        <button onClick={disconnect}>Leave Room</button>\n        <button onClick={toggleCamera}>\n          {isCameraEnabled ? 'Disable Camera' : 'Enable Camera'}\n        </button>\n        <button onClick={toggleMicrophone}>{isMicEnabled ? 'Disable Mic' : 'Enable Mic'}</button>\n      </div>\n\n      {connectionState === 'connected' && (\n        <div className=\"participants\">\n          {/* Local Participant */}\n          {localParticipant && (\n            <div className=\"participant local\">\n              <h3>You (Local)</h3>\n              <video\n                ref={localVideoRef}\n                className=\"w-full h-64 object-cover bg-black\"\n                autoPlay\n                playsInline\n                muted\n              />\n              {/* Hidden audio element for local audio */}\n              <audio ref={localAudioRef} autoPlay muted />\n            </div>\n          )}\n\n          {/* Remote Participants */}\n          {participants.map((participant) => (\n            <div key={participant.sid} className=\"participant\">\n              <h3>{participant.name || participant.identity}</h3>\n              <video\n                ref={(el) => {\n                  if (el) {\n                    remoteVideoRefs.current.set(participant.sid, el);\n                  }\n                }}\n                className=\"w-full h-48 object-cover bg-black\"\n                autoPlay\n                playsInline\n              />\n              {/* Hidden audio element for remote audio */}\n              <audio\n                ref={(el) => {\n                  if (el) {\n                    remoteAudioRefs.current.set(participant.sid, el);\n                  }\n                }}\n                autoPlay\n              />\n            </div>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n}\n```\n\n## Components\n\nAll UI components can be imported from `@bytepulse/pulsewave-client/ui`:\n\n```tsx\nimport { PulseParticipantView, VideoTrack, AudioTrack } from '@bytepulse/pulsewave-client/ui';\n```\n\n### RoomProvider\n\nThe main provider component that wraps your application and provides room context.\n\n```tsx\nimport { RoomProvider } from '@bytepulse/pulsewave-client';\n\n<RoomProvider\n  options={{\n    url: 'ws://localhost:3000',\n    room: 'my-room',\n    token: 'your-access-token',\n  }}\n>\n  <YourApp />\n</RoomProvider>;\n```\n\n### VideoTrack\n\nComponent for rendering video tracks.\n\n```tsx\nimport { VideoTrack } from '@bytepulse/pulsewave-client/ui';\n\n<VideoTrack track={track} className=\"w-full\" objectFit=\"cover\" muted={true} />;\n```\n\n### AudioTrack\n\nComponent for rendering audio tracks (hidden).\n\n```tsx\nimport { AudioTrack } from '@bytepulse/pulsewave-client/ui';\n\n<AudioTrack track={track} />;\n```\n\n## Hooks\n\n### useRoom\n\nAccess the room instance and manage connection.\n\n```tsx\nconst { connect, disconnect } = useRoom();\n\nconnect();\ndisconnect();\n```\n\n### useLocalParticipant\n\nAccess and control the local participant.\n\n```tsx\nconst localParticipant = useLocalParticipant();\n\n// Media controls\nawait localParticipant.enableCamera(deviceId?);\nawait localParticipant.disableCamera();\nawait localParticipant.enableMicrophone(deviceId?);\nawait localParticipant.disableMicrophone();\n\n// Device listing\nconst cameras = await localParticipant.listAvailableCameras();\nconst microphones = await localParticipant.listAvailableMicrophones();\n\n// Properties\nlocalParticipant.name;\nlocalParticipant.identity;\nlocalParticipant.tracks;\n```\n\n### useParticipants\n\nGet all remote participants.\n\n```tsx\nconst participants = useParticipants();\n\nparticipants.map((p) => <ParticipantView key={p.identity} participant={p} />);\n```\n\n### useConnectionState\n\nMonitor connection state.\n\n```tsx\nconst state = useConnectionState();\n// 'connected' | 'connecting' | 'disconnected' | 'reconnecting'\n```\n\n### useMediaDevices\n\nAccess and manage media devices.\n\n```tsx\nconst { audioInputs, videoInputs, audioOutputs, getDevices, getUserMedia, getDisplayMedia } =\n  useMediaDevices();\n\n// Get all devices\nawait getDevices();\n\n// Get user media (camera/mic)\nconst stream = await getUserMedia({ video: true, audio: true });\n\n// Get display media (screen share)\nconst stream = await getDisplayMedia({ video: true });\n```\n\n## Client Architecture\n\nPulseWave Client follows a modular architecture with specialized controllers:\n\n```\nRoomClient (Facade)\n├─ ConnectionController  (WebSocket lifecycle)\n├─ SignalingClient       (Message dispatch)\n├─ MediaController       (Device operations)\n├─ WebRTCController      (WebRTC lifecycle)\n├─ TrackController       (Track publishing/subscription)\n├─ ParticipantStore      (Participant state)\n└─ EventBus              (Event system)\n```\n\n### Controllers\n\n#### EventBus\n\nType-safe event emitter for internal communication.\n\n```typescript\neventBus.emit('participant_joined', participant);\neventBus.on('participant_joined', (participant) => { ... });\n```\n\n#### ParticipantStore\n\nManages participant state with intent-based methods.\n\n```typescript\nparticipantStore.addParticipant(participant);\nparticipantStore.removeParticipant(identity);\nparticipantStore.getLocalParticipant();\nparticipantStore.getRemoteParticipants();\n```\n\n#### ConnectionController\n\nManages WebSocket connection lifecycle.\n\n```typescript\nconnectionController.connect();\nconnectionController.disconnect();\nconnectionController.send(message);\n```\n\n#### SignalingClient\n\nDispatches signaling messages to handlers.\n\n```typescript\nsignalingClient.dispatch(message);\n```\n\n#### MediaController\n\nHandles media device operations.\n\n```typescript\nmediaController.getUserMedia(constraints);\nmediaController.getDisplayMedia(constraints);\nmediaController.getDevices();\n```\n\n#### WebRTCController\n\nManages WebRTC transport lifecycle.\n\n```typescript\nwebRTCController.createTransports();\nwebRTCController.closeTransports();\n```\n\n#### TrackController\n\nHandles track publishing and subscription with high-level APIs.\n\n```typescript\n// High-level methods\ntrackController.enableCamera(deviceId?);\ntrackController.disableCamera();\ntrackController.enableMicrophone(deviceId?);\ntrackController.disableMicrophone();\n\n// Low-level methods\ntrackController.publishTrack(track, options);\ntrackController.unpublishTrack(producerId);\ntrackController.subscribeToTrack(consumerInfo);\n```\n\n## Client Classes\n\n### RoomClient\n\nMain client class that acts as a facade/coordinator for all subsystems.\n\n```tsx\nimport { RoomClient } from '@bytepulse/pulsewave-client';\n\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n});\n\nawait client.connect();\nawait client.disconnect();\n```\n\n### LocalParticipant\n\nRepresents the local user in the room.\n\n```tsx\nconst localParticipant = client.localParticipant;\n\n// Media controls\nawait localParticipant.enableCamera();\nawait localParticipant.disableCamera();\nawait localParticipant.enableMicrophone();\nawait localParticipant.disableMicrophone();\n\n// Device listing\nconst cameras = await localParticipant.listAvailableCameras();\nconst microphones = await localParticipant.listAvailableMicrophones();\n\n// Properties\nlocalParticipant.name;\nlocalParticipant.identity;\nlocalParticipant.tracks;\n```\n\n### Participant\n\nBase participant class.\n\n```tsx\nparticipant.name;\nparticipant.identity;\nparticipant.tracks;\n```\n\n### Track\n\nBase track class.\n\n```tsx\ntrack.mute();\ntrack.unmute();\ntrack.stop();\n```\n\n### LocalTrack\n\nLocal track implementation.\n\n```tsx\nawait localTrack.mute();\nawait localTrack.unmute();\n```\n\n### RemoteTrack\n\nRemote track implementation.\n\n```tsx\nremoteTrack.on('subscribed', () => console.log('Subscribed'));\nremoteTrack.on('unsubscribed', () => console.log('Unsubscribed'));\n```\n\n### DataChannel\n\nWebRTC data channel wrapper.\n\n```tsx\ndataChannel.send('Hello');\ndataChannel.on('message', (data) => console.log(data));\n```\n\n### DataChannelManager\n\nManages multiple data channels.\n\n```tsx\nconst channel = await manager.createChannel({ label: 'chat' });\nmanager.closeChannel('chat');\n```\n\n## Types\n\n### RoomClientOptions\n\n```tsx\ninterface RoomClientOptions {\n  url: string;\n  room: string;\n  token: string;\n  autoSubscribe?: boolean;\n  preferredVideoCodec?: 'vp8' | 'vp9' | 'h264' | 'av1';\n  preferredAudioCodec?: 'opus' | 'isac' | 'aac';\n  enableSimulcast?: boolean;\n  enableDataChannels?: boolean;\n  maxVideoBitrate?: number;\n  maxAudioBitrate?: number;\n  iceServers?: RTCIceServer[];\n  iceTransportPolicy?: 'all' | 'relay';\n  debug?: boolean;\n  dataProvider?: DataProviderConfig | DataProviderType;\n}\n\ntype DataProviderType = 'WebSocket' | 'WebRTC';\n\ninterface DataProviderConfig {\n  type: DataProviderType;\n  maxMessageSize?: number;\n  ordered?: boolean;\n  maxPacketLifeTime?: number;\n  maxRetransmits?: number;\n}\n```\n\n#### Data Provider Options\n\nPulseWave supports two data transmission methods for data channels:\n\n**WebSocket (Default)** - Data flows through WebSocket signaling layer, routed through the server to all participants.\n\n```tsx\n// Default - no configuration needed\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n  // dataProvider defaults to WebSocket\n});\n\n// Explicit WebSocket configuration\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n  dataProvider: 'WebSocket',\n});\n\n// With custom options\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n  dataProvider: {\n    type: 'WebSocket',\n    maxMessageSize: 16384, // 16KB default\n  },\n});\n```\n\n**WebRTC (Experimental)** - Uses mediasoup's DataProducer/DataConsumer for peer-to-peer data transmission through the SFU.\n\n```tsx\n// Default WebRTC configuration\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n  dataProvider: 'WebRTC',\n});\n\n// With custom options\nconst client = new RoomClient({\n  url: 'ws://localhost:3000',\n  room: 'my-room',\n  token: 'your-token',\n  dataProvider: {\n    type: 'WebRTC',\n    maxMessageSize: 262144, // 256KB default\n    ordered: true,\n    maxPacketLifeTime: 1000,\n    maxRetransmits: 3,\n  },\n});\n```\n\n**Choosing Between Data Providers:**\n\n| Feature      | WebSocket                | WebRTC                      |\n| ------------ | ------------------------ | --------------------------- |\n| Latency      | Higher (server relay)    | Lower (P2P through SFU)     |\n| Scalability  | Better (server managed)  | Limited (P2P connections)   |\n| Reliability  | High (server guaranteed) | Medium (depends on network) |\n| Message Size | 16KB default             | 256KB default               |\n| Status       | Stable                   | Experimental                |\n\n### TrackPublishOptions\n\n```tsx\ninterface TrackPublishOptions {\n  simulcast?: boolean;\n  maxBitrate?: number;\n  codec?: string;\n}\n```\n\n### TrackSubscribeOptions\n\n```tsx\ninterface TrackSubscribeOptions {\n  codec?: string;\n  maxBitrate?: number;\n}\n```\n\n### DataChannelOptions\n\n```tsx\ninterface DataChannelOptions {\n  label: string;\n  ordered?: boolean;\n  maxPacketLifeTime?: number;\n  maxRetransmits?: number;\n  protocol?: string;\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}