From d8e993842ad01b7d142bee95f8109aabcd0640ac Mon Sep 17 00:00:00 2001 From: jmoguilevsky Date: Fri, 4 Aug 2023 18:30:19 -0300 Subject: [PATCH] full integration --- package-lock.json | 308 ++++++++++++++++++ package.json | 1 + .../Pages/Conference/Conference.container.ts | 24 ++ .../Pages/Conference/Conference.tsx | 15 + .../Pages/Conference/Conference.types.ts | 19 ++ src/components/Pages/Conference/index.ts | 2 + .../Pages/MainPage/MainPage.container.ts | 11 +- src/components/Pages/MainPage/MainPage.tsx | 91 ++++-- .../Pages/MainPage/MainPage.types.ts | 15 +- src/main.tsx | 5 + src/modules/conference/action.ts | 9 + src/modules/conference/reducer.ts | 22 ++ src/modules/conference/selector.ts | 9 + src/modules/reducer.ts | 3 + src/modules/routing/locations.ts | 7 +- src/utils/auth.ts | 121 +++++++ src/utils/client-utils.ts | 21 ++ src/utils/flat-fetch.ts | 40 +++ src/utils/server-utils.ts | 27 ++ src/utils/types.ts | 16 + 20 files changed, 731 insertions(+), 35 deletions(-) create mode 100644 src/components/Pages/Conference/Conference.container.ts create mode 100644 src/components/Pages/Conference/Conference.tsx create mode 100644 src/components/Pages/Conference/Conference.types.ts create mode 100644 src/components/Pages/Conference/index.ts create mode 100644 src/modules/conference/action.ts create mode 100644 src/modules/conference/reducer.ts create mode 100644 src/modules/conference/selector.ts create mode 100644 src/utils/auth.ts create mode 100644 src/utils/client-utils.ts create mode 100644 src/utils/flat-fetch.ts create mode 100644 src/utils/server-utils.ts create mode 100644 src/utils/types.ts diff --git a/package-lock.json b/package-lock.json index e0fc485..000f067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0995064..6945c8a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Pages/Conference/Conference.container.ts b/src/components/Pages/Conference/Conference.container.ts new file mode 100644 index 0000000..8551f67 --- /dev/null +++ b/src/components/Pages/Conference/Conference.container.ts @@ -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)) diff --git a/src/components/Pages/Conference/Conference.tsx b/src/components/Pages/Conference/Conference.tsx new file mode 100644 index 0000000..ae99abd --- /dev/null +++ b/src/components/Pages/Conference/Conference.tsx @@ -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 ( + <> + + + + + ) +} diff --git a/src/components/Pages/Conference/Conference.types.ts b/src/components/Pages/Conference/Conference.types.ts new file mode 100644 index 0000000..5038d25 --- /dev/null +++ b/src/components/Pages/Conference/Conference.types.ts @@ -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 +export type MapDispatch = Dispatch +type Params = { + profileAddress?: string +} +export type OwnProps = { + router: RouterProps +} diff --git a/src/components/Pages/Conference/index.ts b/src/components/Pages/Conference/index.ts new file mode 100644 index 0000000..8316fe6 --- /dev/null +++ b/src/components/Pages/Conference/index.ts @@ -0,0 +1,2 @@ +import Conference from "./Conference.container" +export default Conference diff --git a/src/components/Pages/MainPage/MainPage.container.ts b/src/components/Pages/MainPage/MainPage.container.ts index 7bab6c7..244051b 100644 --- a/src/components/Pages/MainPage/MainPage.container.ts +++ b/src/components/Pages/MainPage/MainPage.container.ts @@ -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)) diff --git a/src/components/Pages/MainPage/MainPage.tsx b/src/components/Pages/MainPage/MainPage.tsx index 36acc8d..d4feb8e 100644 --- a/src/components/Pages/MainPage/MainPage.tsx +++ b/src/components/Pages/MainPage/MainPage.tsx @@ -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(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) => { + 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 ( - {isLoading ? ( + {isLoading || isConnectingToServer ? ( ) : (
- - {tabs.map((tab) => ( - handleTabChange(tab.value)}> - {tab.displayValue} - - ))} - - - - + +
+ + + +
)} diff --git a/src/components/Pages/MainPage/MainPage.types.ts b/src/components/Pages/MainPage/MainPage.types.ts index 0447dad..7c478f8 100644 --- a/src/components/Pages/MainPage/MainPage.types.ts +++ b/src/components/Pages/MainPage/MainPage.types.ts @@ -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 -export type MapDispatchProps = Pick -export type MapDispatch = Dispatch +export type MapStateProps = Pick +export type MapDispatchProps = Pick +export type MapDispatch = Dispatch type Params = { profileAddress?: string } diff --git a/src/main.tsx b/src/main.tsx index 5bcbae4..a7db6c0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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: , }, + { + path: "/meet/:server", + element: , + }, ]) const component = ( diff --git a/src/modules/conference/action.ts b/src/modules/conference/action.ts new file mode 100644 index 0000000..399624e --- /dev/null +++ b/src/modules/conference/action.ts @@ -0,0 +1,9 @@ +import { createAction } from "@reduxjs/toolkit" + +export const setServer = createAction<{ server: string }>("Set Server") + +export type SetServerAction = ReturnType + +export const setToken = createAction<{ token: string }>("Set Token") + +export type SetTokenAction = ReturnType diff --git a/src/modules/conference/reducer.ts b/src/modules/conference/reducer.ts new file mode 100644 index 0000000..57197e5 --- /dev/null +++ b/src/modules/conference/reducer.ts @@ -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(INITIAL_STATE, (builder) => + builder + .addCase(setServer, (state, action) => { + state.server = action.payload.server + }) + .addCase(setToken, (state, action) => { + state.token = action.payload.token + }) +) diff --git a/src/modules/conference/selector.ts b/src/modules/conference/selector.ts new file mode 100644 index 0000000..1fb1729 --- /dev/null +++ b/src/modules/conference/selector.ts @@ -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) diff --git a/src/modules/reducer.ts b/src/modules/reducer.ts index 488db7a..eb6214a 100644 --- a/src/modules/reducer.ts +++ b/src/modules/reducer.ts @@ -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, profile, identity, + conference, }) ), preloadedState, @@ -54,6 +56,7 @@ export type RootState = { translation: TranslationState wallet: WalletState features: FeaturesState + conference: ConferenceState } export type RootStore = Store diff --git a/src/modules/routing/locations.ts b/src/modules/routing/locations.ts index 86f5d81..27b8cb7 100644 --- a/src/modules/routing/locations.ts +++ b/src/modules/routing/locations.ts @@ -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}`, } diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..6abc146 --- /dev/null +++ b/src/utils/auth.ts @@ -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 +} + +export async function getUserAccount( + requestManager: RequestManager, + returnChecksum: boolean +): Promise { + 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, + chainProvider: (payload: string) => AuthChain +) { + const headers: Record = {} + 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 = {} +) { + 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 +): Promise { + const ephemeral = createUnsafeIdentity() + + const authChain = await Authenticator.initializeAuthChain(address, ephemeral, ephemeralLifespanMinutes, signer) + + return { + address, + signer, + authChain, + isGuest: true, + } +} diff --git a/src/utils/client-utils.ts b/src/utils/client-utils.ts new file mode 100644 index 0000000..cdee23f --- /dev/null +++ b/src/utils/client-utils.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react" + +export function useServerUrl(region?: string) { + const [serverUrl, setServerUrl] = useState() + 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 +} diff --git a/src/utils/flat-fetch.ts b/src/utils/flat-fetch.ts new file mode 100644 index 0000000..32c9cf1 --- /dev/null +++ b/src/utils/flat-fetch.ts @@ -0,0 +1,40 @@ +type FlatFetchResponse = { + ok: boolean + status: number + statusText: string + headers: Record + json?: any + text?: string +} + +type BodyType = "json" | "text" + +export type FlatFetchInit = RequestInit & { responseBodyType?: BodyType } + +export async function flatFetch(url: string, init?: FlatFetchInit): Promise { + const response = await fetch(url, init) + + const responseBodyType = init?.responseBodyType || "text" + + const headers: Record = {} + + 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 +} diff --git a/src/utils/server-utils.ts b/src/utils/server-utils.ts new file mode 100644 index 0000000..6d91daa --- /dev/null +++ b/src/utils/server-utils.ts @@ -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") + } +} diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..9a1a6c2 --- /dev/null +++ b/src/utils/types.ts @@ -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 +}