full integration

This commit is contained in:
jmoguilevsky 2023-08-04 18:30:19 -03:00
parent 8da838e893
commit d8e993842a
No known key found for this signature in database
GPG Key ID: D097BD46EA6103FA
20 changed files with 731 additions and 35 deletions

308
package-lock.json generated
View File

@ -20,6 +20,7 @@
"decentraland-ui": "^4.0.0",
"ethers": "^6.6.5",
"livekit-client": "^1.12.3",
"livekit-server-sdk": "^1.2.5",
"p-queue": "^6.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@ -7256,6 +7257,11 @@
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
@ -7366,6 +7372,45 @@
"node": ">=6"
}
},
"node_modules/camelcase-keys": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz",
"integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==",
"dependencies": {
"camelcase": "^6.3.0",
"map-obj": "^4.1.0",
"quick-lru": "^5.1.1",
"type-fest": "^1.2.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase-keys/node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase-keys/node_modules/type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001514",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
@ -8692,6 +8737,14 @@
"node": ">=4.0.0"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -14179,6 +14232,21 @@
"node": "*"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz",
"integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==",
"dependencies": {
"jws": "^3.2.2",
"lodash": "^4.17.21",
"ms": "^2.1.1",
"semver": "^7.3.8"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@ -14193,6 +14261,25 @@
"node": ">=0.6.0"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keccak": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz",
@ -14417,6 +14504,73 @@
"webrtc-adapter": "^8.1.1"
}
},
"node_modules/livekit-server-sdk": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-1.2.5.tgz",
"integrity": "sha512-QYHGEoilSAXUQQBAZE2SXU1oXW8z08VFp2UxcZzXdPt3u4E9xamghTMhgniLMWmpSCl7oqVObQ6XXnK9rkr0Pg==",
"dependencies": {
"axios": "^1.3.6",
"camelcase-keys": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"protobufjs": "^7.2.4"
}
},
"node_modules/livekit-server-sdk/node_modules/@types/node": {
"version": "20.4.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz",
"integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g=="
},
"node_modules/livekit-server-sdk/node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/livekit-server-sdk/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/livekit-server-sdk/node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"node_modules/livekit-server-sdk/node_modules/protobufjs": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz",
"integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -14608,6 +14762,17 @@
"tmpl": "1.0.5"
}
},
"node_modules/map-obj": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/matchmediaquery": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz",
@ -16017,6 +16182,11 @@
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz",
"integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA=="
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@ -25071,6 +25241,11 @@
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
@ -25153,6 +25328,29 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"camelcase-keys": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz",
"integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==",
"requires": {
"camelcase": "^6.3.0",
"map-obj": "^4.1.0",
"quick-lru": "^5.1.1",
"type-fest": "^1.2.1"
},
"dependencies": {
"camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
},
"type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
}
}
},
"caniuse-lite": {
"version": "1.0.30001514",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
@ -26172,6 +26370,14 @@
}
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -30320,6 +30526,17 @@
"through": ">=2.2.7 <3"
}
},
"jsonwebtoken": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz",
"integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==",
"requires": {
"jws": "^3.2.2",
"lodash": "^4.17.21",
"ms": "^2.1.1",
"semver": "^7.3.8"
}
},
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@ -30331,6 +30548,25 @@
"verror": "1.10.0"
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"keccak": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz",
@ -30542,6 +30778,68 @@
"webrtc-adapter": "^8.1.1"
}
},
"livekit-server-sdk": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-1.2.5.tgz",
"integrity": "sha512-QYHGEoilSAXUQQBAZE2SXU1oXW8z08VFp2UxcZzXdPt3u4E9xamghTMhgniLMWmpSCl7oqVObQ6XXnK9rkr0Pg==",
"requires": {
"axios": "^1.3.6",
"camelcase-keys": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"protobufjs": "^7.2.4"
},
"dependencies": {
"@types/node": {
"version": "20.4.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz",
"integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g=="
},
"axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
},
"protobufjs": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz",
"integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
}
}
}
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -30701,6 +30999,11 @@
"tmpl": "1.0.5"
}
},
"map-obj": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="
},
"matchmediaquery": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz",
@ -31771,6 +32074,11 @@
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.5.1.tgz",
"integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA=="
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",

View File

@ -28,6 +28,7 @@
"decentraland-ui": "^4.0.0",
"ethers": "^6.6.5",
"livekit-client": "^1.12.3",
"livekit-server-sdk": "^1.2.5",
"p-queue": "^6.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",

View File

@ -0,0 +1,24 @@
import { connect } from "react-redux"
import { getAddress, isConnecting } from "decentraland-dapps/dist/modules/wallet/selectors"
import { isLoggingIn } from "../../../modules/identity/selector"
import { getServer, getToken } from "../../../modules/conference/selector"
import { RootState } from "../../../modules/reducer"
import withRouter from "../../../utils/WithRouter"
import Conference from "./Conference"
import { MapDispatch, MapStateProps, OwnProps } from "./Conference.types"
const mapStateToProps = (state: RootState, ownProps: OwnProps): MapStateProps => {
const addressFromPath = ownProps.router.params.profileAddress
return {
profileAddress: addressFromPath?.toLowerCase(),
isLoading: isLoggingIn(state) || isConnecting(state),
loggedInAddress: getAddress(state)?.toLowerCase(),
server: getServer(state),
token: getToken(state),
}
}
const mapDispatch = (dispatch: MapDispatch): any => ({})
export default withRouter(connect(mapStateToProps, mapDispatch)(Conference))

View File

@ -0,0 +1,15 @@
import React from "react"
import { Props } from "./Conference.types"
import { LiveKitRoom, VideoConference } from "@livekit/components-react"
export default function Conference(props: Props) {
const { token, server } = props
return (
<>
<LiveKitRoom token={token} serverUrl={server} connect={true}>
<VideoConference />
</LiveKitRoom>
</>
)
}

View File

@ -0,0 +1,19 @@
import { Dispatch } from "redux"
import { RouterProps } from "../../../utils/WithRouter"
export type Props = {
loggedInAddress?: string
profileAddress?: string
isLoading: boolean
server?: string
token?: string
}
export type MapStateProps = Pick<Props, "loggedInAddress" | "isLoading" | "profileAddress" | "server" | "token">
export type MapDispatch = Dispatch
type Params = {
profileAddress?: string
}
export type OwnProps = {
router: RouterProps<Params>
}

View File

@ -0,0 +1,2 @@
import Conference from "./Conference.container"
export default Conference

View File

@ -1,24 +1,29 @@
import { connect } from "react-redux"
import { loadProfileRequest } from "decentraland-dapps/dist/modules/profile/actions"
import { getAddress, isConnecting } from "decentraland-dapps/dist/modules/wallet/selectors"
import { isLoggingIn } from "../../../modules/identity/selector"
import { getCurrentIdentity, isLoggingIn } from "../../../modules/identity/selector"
import { RootState } from "../../../modules/reducer"
import withRouter from "../../../utils/WithRouter"
import MainPage from "./MainPage"
import { MapDispatch, MapDispatchProps, MapStateProps, OwnProps } from "./MainPage.types"
import { setServer, setToken } from "../../../modules/conference/action"
const mapStateToProps = (state: RootState, ownProps: OwnProps): MapStateProps => {
const addressFromPath = ownProps.router.params.profileAddress
const identity = getCurrentIdentity(state)
return {
profileAddress: addressFromPath?.toLowerCase(),
isLoading: isLoggingIn(state) || isConnecting(state),
loggedInAddress: getAddress(state)?.toLowerCase(),
identity,
}
}
const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onFetchProfile: (address) => dispatch(loadProfileRequest(address)),
onSubmitConnectForm: (server: string, token:string) => {
dispatch(setServer({ server }))
dispatch(setToken({ token }))
},
})
export default withRouter(connect(mapStateToProps, mapDispatch)(MainPage))

View File

@ -1,30 +1,26 @@
import React, { useCallback, useEffect, useState } from "react"
import React, { ChangeEvent, useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"
import Divider from "semantic-ui-react/dist/commonjs/elements/Divider/Divider"
import { t } from "decentraland-dapps/dist/modules/translation/utils"
import { Tabs } from "decentraland-ui/dist/components/Tabs/Tabs"
import { Loader } from "decentraland-ui"
import { locations } from "../../../modules/routing/locations"
import { getView } from "../../../utils/view"
import { PageLayout } from "../../PageLayout"
import { Props } from "./MainPage.types"
import styles from "./MainPage.module.css"
import { LiveKitRoom, VideoConference } from "@livekit/components-react"
import { flatFetch } from "../../../utils/flat-fetch"
import { signedFetch } from "../../../utils/auth"
import { AuthIdentity } from "@dcl/crypto"
function MainPage(props: Props) {
const { isLoading, onFetchProfile, profileAddress, loggedInAddress } = props
const [selectedServer, setSelectedServer] = useState("")
const [isConnectingToServer, setIsConnectingToServer] = useState(false)
const { isLoading, onFetchProfile, profileAddress, loggedInAddress, identity, onSubmitConnectForm } = props
const tabs: { displayValue: string; value: string }[] = [
{ displayValue: t("tabs.overview"), value: t("tabs.overview") },
]
const [selectedTab, setSelectedTab] = useState<string>(tabs[0].value)
const handleTabChange = useCallback(
(tab: string) => {
setSelectedTab(tab)
},
[setSelectedTab]
)
const navigate = useNavigate()
useEffect(() => {
@ -40,24 +36,73 @@ function MainPage(props: Props) {
}, [isLoading, loggedInAddress, profileAddress])
const view = getView(loggedInAddress, profileAddress)
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setSelectedServer(e.target.value)
}
async function livekitConnect(identity: AuthIdentity, worldServer: string, worldName: string) {
const aboutResponse = await flatFetch(`${worldServer}/world/${worldName}/about`)
console.log(aboutResponse.text)
if (aboutResponse.status === 200) {
const url = JSON.parse(aboutResponse.text!)
["comms"]["fixedAdapter"].replace("signed-login:", "")
.replace("get-comms-adapter", "cast-adapter")
const response = await signedFetch(
url,
identity,
{
method: "POST",
},
{
signer: "dcl:explorer",
intent: "dcl:explorer:comms-handshake",
}
)
if (response.status === 200) {
console.log(response.text)
return JSON.parse(response.text!)
} else {
let message = ""
try {
message = JSON.parse(response.text || "")?.message
} catch (e) {
message = response.text || ""
}
throw Error(message)
}
// throw Error(`Failed to connect to LiveKit: ${JSON.stringify(response.text || response.json?.message)}`)
} else if (aboutResponse.status === 404) {
throw Error(`World ${worldName} not found`)
}
throw Error("An error has occurred")
}
const handleClick = () => {
livekitConnect(identity!, "https://worlds-content-server.decentraland.zone", selectedServer)
.then((response: { url: string; token: string }) => {
onSubmitConnectForm(response.url, response.token)
navigate(`/meet/${encodeURIComponent(response.url)}?token=${encodeURIComponent(response.token)}`)
})
.catch((err) => {
console.error("ERROR livekit connect", err)
})
}
return (
<PageLayout>
{isLoading ? (
{isLoading || isConnectingToServer ? (
<Loader active />
) : (
<div className={styles.MainPage}>
<div className={styles.infoContainer}>
<Divider />
<Tabs>
{tabs.map((tab) => (
<Tabs.Tab key={tab.value} active={selectedTab === tab.value} onClick={() => handleTabChange(tab.value)}>
<span className={styles.tab}>{tab.displayValue}</span>
</Tabs.Tab>
))}
</Tabs>
<LiveKitRoom token="" serverUrl="wss://dcl.livekit.cloud" connect={true}>
<VideoConference />
</LiveKitRoom>
<div>
<input name="server" value={selectedServer} onChange={handleChange} />
<button onClick={handleClick}>Connect</button>
</div>
</div>
</div>
)}

View File

@ -1,17 +1,20 @@
import { Dispatch } from 'redux'
import { LoadProfileRequestAction, loadProfileRequest } from 'decentraland-dapps/dist/modules/profile/actions'
import { RouterProps } from '../../../utils/WithRouter'
import { Dispatch } from "redux"
import { loadProfileRequest } from "decentraland-dapps/dist/modules/profile/actions"
import { RouterProps } from "../../../utils/WithRouter"
import { AuthIdentity } from "@dcl/crypto"
export type Props = {
onFetchProfile: typeof loadProfileRequest
loggedInAddress?: string
profileAddress?: string
isLoading: boolean
onSubmitConnectForm: (server: string, token: string) => void
identity: AuthIdentity | null
}
export type MapStateProps = Pick<Props, 'loggedInAddress' | 'isLoading' | 'profileAddress'>
export type MapDispatchProps = Pick<Props, 'onFetchProfile'>
export type MapDispatch = Dispatch<LoadProfileRequestAction>
export type MapStateProps = Pick<Props, "loggedInAddress" | "isLoading" | "profileAddress" | "identity">
export type MapDispatchProps = Pick<Props, "onSubmitConnectForm">
export type MapDispatch = Dispatch
type Params = {
profileAddress?: string
}

View File

@ -15,6 +15,7 @@ import * as locales from "./modules/translation/locales"
// These CSS styles must be defined last to avoid overriding other styles
import "decentraland-ui/dist/themes/alternative/dark-theme.css"
import "./index.css"
import Conference from "./components/Pages/Conference"
const router = createBrowserRouter([
{
@ -29,6 +30,10 @@ const router = createBrowserRouter([
path: "sign-in",
element: <SignInPage />,
},
{
path: "/meet/:server",
element: <Conference />,
},
])
const component = (

View File

@ -0,0 +1,9 @@
import { createAction } from "@reduxjs/toolkit"
export const setServer = createAction<{ server: string }>("Set Server")
export type SetServerAction = ReturnType<typeof setServer>
export const setToken = createAction<{ token: string }>("Set Token")
export type SetTokenAction = ReturnType<typeof setToken>

View File

@ -0,0 +1,22 @@
import { createReducer } from "@reduxjs/toolkit"
import { setServer, setToken } from "./action"
export type ConferenceState = {
token: string
server: string
}
export const INITIAL_STATE: ConferenceState = {
token: "",
server: "",
}
export const conferenceReducer = createReducer<ConferenceState>(INITIAL_STATE, (builder) =>
builder
.addCase(setServer, (state, action) => {
state.server = action.payload.server
})
.addCase(setToken, (state, action) => {
state.token = action.payload.token
})
)

View File

@ -0,0 +1,9 @@
import { createSelector } from "@reduxjs/toolkit"
import { RootState } from "../reducer"
const getState = (state: RootState) => state.conference
export const getToken = (state: RootState) => getState(state).token
export const getServer = (state: RootState) => getState(state).server
export const isLoading = createSelector([getToken], (token) => !!token)

View File

@ -13,6 +13,7 @@ import {
} from "decentraland-dapps/dist/modules/translation/reducer"
import { WalletState, walletReducer as wallet } from "decentraland-dapps/dist/modules/wallet/reducer"
import { IdentityState, identityReducer as identity } from "./identity/reducer"
import { ConferenceState, conferenceReducer as conference } from "./conference/reducer"
export const createRootReducer = (middlewares: Middleware[], preloadedState = {}) =>
configureStore({
@ -25,6 +26,7 @@ export const createRootReducer = (middlewares: Middleware[], preloadedState = {}
translation: translation as Reducer<TranslationState, AnyAction>,
profile,
identity,
conference,
})
),
preloadedState,
@ -54,6 +56,7 @@ export type RootState = {
translation: TranslationState
wallet: WalletState
features: FeaturesState
conference: ConferenceState
}
export type RootStore = Store<RootState>

View File

@ -1,7 +1,8 @@
export const locations = {
root: () => '/',
root: () => "/",
account: (address: string) => `/accounts/${address}`,
signIn: (redirectTo?: string) => {
return `/sign-in${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''}`
}
return `/sign-in${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ""}`
},
meet: (server: string, token: string) => `/meet/${server}?token${token}`,
}

121
src/utils/auth.ts Normal file
View File

@ -0,0 +1,121 @@
import { RequestManager } from "eth-connect"
import { createUnsafeIdentity } from "@dcl/crypto/dist/crypto"
import { FlatFetchInit, flatFetch } from "./flat-fetch"
import { AuthChain, AuthIdentity, Authenticator } from "@dcl/crypto"
const ephemeralLifespanMinutes = 10_000
export type ExplorerIdentity = {
// public address of the first elemement of the authChain
address: string
// is the address an ephemeral address? used to determine if the user is a guest
isGuest: boolean
// the authChain is the chain of signatures that prove the ownership of the address
authChain: AuthIdentity
// the signer function will be used to sign messages using the last element of the authChain
signer: (message: string) => Promise<string>
}
export async function getUserAccount(
requestManager: RequestManager,
returnChecksum: boolean
): Promise<string | undefined> {
try {
const accounts = await requestManager.eth_accounts()
if (!accounts || accounts.length === 0) {
return undefined
}
return returnChecksum ? accounts[0] : accounts[0].toLowerCase()
} catch (error: any) {
throw new Error(`Could not access eth_accounts: "${error.message}"`)
}
}
const AUTH_CHAIN_HEADER_PREFIX = "x-identity-auth-chain-"
const AUTH_TIMESTAMP_HEADER = "x-identity-timestamp"
const AUTH_METADATA_HEADER = "x-identity-metadata"
export function getAuthChainSignature(
method: string,
path: string,
metadata: string,
chainProvider: (payload: string) => AuthChain
) {
const timestamp = Date.now()
const payloadParts = [method.toLowerCase(), path.toLowerCase(), timestamp.toString(), metadata]
const payloadToSign = payloadParts.join(":").toLowerCase()
const authChain = chainProvider(payloadToSign)
return {
authChain,
metadata,
timestamp,
}
}
export function getSignedHeaders(
method: string,
path: string,
metadata: Record<string, any>,
chainProvider: (payload: string) => AuthChain
) {
const headers: Record<string, string> = {}
const signature = getAuthChainSignature(method, path, JSON.stringify(metadata), chainProvider)
signature.authChain.forEach((link, index) => {
headers[`${AUTH_CHAIN_HEADER_PREFIX}${index}`] = JSON.stringify(link)
})
headers[AUTH_TIMESTAMP_HEADER] = signature.timestamp.toString()
headers[AUTH_METADATA_HEADER] = signature.metadata
return headers
}
export function signedFetch(
url: string,
identity: AuthIdentity,
init?: FlatFetchInit,
additionalMetadata: Record<string, any> = {}
) {
const path = new URL(url).pathname
const actualInit = {
...init,
headers: {
...getSignedHeaders(
init?.method ?? "get",
path,
{
origin: location.origin,
...additionalMetadata,
},
(payload) => Authenticator.signPayload(identity, payload)
),
...init?.headers,
},
} as FlatFetchInit
return flatFetch(url, actualInit)
}
// this function creates a Decentraland AuthChain using a signer function.
// the signer function is only used once, to sign the ephemeral private key. after that,
// the ephemeral private key is used to sign the rest of the authChain and subsequent
// messages. this is a good way to not over-expose the real user accounts to excessive
// signing requests.
export async function identityFromSigner(
address: string,
signer: (message: string) => Promise<string>
): Promise<ExplorerIdentity> {
const ephemeral = createUnsafeIdentity()
const authChain = await Authenticator.initializeAuthChain(address, ephemeral, ephemeralLifespanMinutes, signer)
return {
address,
signer,
authChain,
isGuest: true,
}
}

21
src/utils/client-utils.ts Normal file
View File

@ -0,0 +1,21 @@
import { useEffect, useState } from "react"
export function useServerUrl(region?: string) {
const [serverUrl, setServerUrl] = useState<string | undefined>()
useEffect(() => {
let endpoint = `/api/url`
if (region) {
endpoint += `?region=${region}`
}
fetch(endpoint).then(async (res) => {
if (res.ok) {
const body = await res.json()
console.log(body)
setServerUrl(body.url)
} else {
throw Error("Error fetching server url, check server logs")
}
})
})
return serverUrl
}

40
src/utils/flat-fetch.ts Normal file
View File

@ -0,0 +1,40 @@
type FlatFetchResponse = {
ok: boolean
status: number
statusText: string
headers: Record<string, string>
json?: any
text?: string
}
type BodyType = "json" | "text"
export type FlatFetchInit = RequestInit & { responseBodyType?: BodyType }
export async function flatFetch(url: string, init?: FlatFetchInit): Promise<FlatFetchResponse> {
const response = await fetch(url, init)
const responseBodyType = init?.responseBodyType || "text"
const headers: Record<string, string> = {}
response.headers.forEach((value, key) => (headers[key] = value))
const flatFetchResponse: FlatFetchResponse = {
ok: response.ok,
status: response.status,
statusText: response.statusText,
headers,
}
switch (responseBodyType) {
case "json":
flatFetchResponse.json = await response.json()
break
case "text":
flatFetchResponse.text = await response.text()
break
}
return flatFetchResponse
}

27
src/utils/server-utils.ts Normal file
View File

@ -0,0 +1,27 @@
import { RoomServiceClient } from "livekit-server-sdk"
export function getRoomClient(): RoomServiceClient {
checkKeys()
return new RoomServiceClient(getLiveKitURL())
}
export function getLiveKitURL(region?: string | string[]): string {
let targetKey = "LIVEKIT_URL"
if (region && !Array.isArray(region)) {
targetKey = `LIVEKIT_URL_${region}`.toUpperCase()
}
const url = process.env[targetKey]
if (!url) {
throw new Error(`${targetKey} is not defined`)
}
return url
}
function checkKeys() {
if (typeof process.env.LIVEKIT_API_KEY === "undefined") {
throw new Error("LIVEKIT_API_KEY is not defined")
}
if (typeof process.env.LIVEKIT_API_SECRET === "undefined") {
throw new Error("LIVEKIT_API_SECRET is not defined")
}
}

16
src/utils/types.ts Normal file
View File

@ -0,0 +1,16 @@
import { LocalAudioTrack, LocalVideoTrack } from "livekit-client"
export interface SessionProps {
roomName: string
identity: string
audioTrack?: LocalAudioTrack
videoTrack?: LocalVideoTrack
region?: string
turnServer?: RTCIceServer
forceRelay?: boolean
}
export interface TokenResult {
identity: string
accessToken: string
}