From 9209529958425054a72520d53c5fce1886ddb0d8 Mon Sep 17 00:00:00 2001 From: Hugo Arregui Date: Tue, 12 Jul 2022 16:29:08 -0300 Subject: [PATCH] feat: Decouple Comms Services (#269) * comms v3 Co-authored-by: Juan Scolari Co-authored-by: Mateo Miccino Co-authored-by: menduz --- .gitignore | 2 +- Makefile | 26 +- package-lock.json | 164 ++++++- package.json | 1 + packages/shared/comms/context.ts | 1 + packages/shared/comms/index.ts | 24 +- packages/shared/comms/v3/resolver.ts | 28 +- packages/shared/comms/v4/BFFConnection.ts | 194 ++++++++ .../shared/comms/v4/InstanceConnection.ts | 433 ++++++++++++++++++ .../shared/comms/v4/proto/archipelago.proto | 30 ++ .../v4/proto/bff/authentication-service.proto | 23 + .../comms/v4/proto/bff/comms-service.proto | 47 ++ packages/shared/comms/v4/proto/comms.proto | 64 +++ packages/shared/comms/v4/types.ts | 1 + packages/shared/dao/sagas.ts | 14 +- 15 files changed, 1019 insertions(+), 33 deletions(-) create mode 100644 packages/shared/comms/v4/BFFConnection.ts create mode 100644 packages/shared/comms/v4/InstanceConnection.ts create mode 100644 packages/shared/comms/v4/proto/archipelago.proto create mode 100644 packages/shared/comms/v4/proto/bff/authentication-service.proto create mode 100644 packages/shared/comms/v4/proto/bff/comms-service.proto create mode 100644 packages/shared/comms/v4/proto/comms.proto create mode 100644 packages/shared/comms/v4/types.ts diff --git a/.gitignore b/.gitignore index 7a554f71a..d2299ff6e 100644 --- a/.gitignore +++ b/.gitignore @@ -97,4 +97,4 @@ static/package-lock.json packages/shared/apis/proto/*.ts lsall -*.gen.ts \ No newline at end of file +*.gen.ts diff --git a/Makefile b/Makefile index 26f4571dc..b66dbcfba 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,16 @@ endif NODE = node COMPILER = $(NODE) --max-old-space-size=4096 node_modules/.bin/decentraland-compiler CONCURRENTLY = node_modules/.bin/concurrently +BFF_PROTO_FILES := $(wildcard packages/shared/comms/v4/proto/bff/*.proto) +COMMS_PROTO_FILES := $(wildcard packages/shared/comms/v4/proto/*.proto) + SCENE_PROTO_FILES := $(wildcard node_modules/@dcl/protocol/kernel/apis/*.proto) RENDERER_PROTO_FILES := $(wildcard node_modules/@dcl/protocol/renderer-protocol/*.proto) PBS_TS = $(SCENE_PROTO_FILES:node_modules/@dcl/protocol/kernel/apis/%.proto=packages/shared/apis/proto/%.gen.ts) PBRENDERER_TS = $(RENDERER_PROTO_FILES:node_modules/@dcl/protocol/renderer-protocol/%.proto=packages/renderer-protocol/proto/%.gen.ts) +BFF_TS = $(BFF_PROTO_FILES:packages/shared/comms/v4/proto/bff/%.proto=packages/shared/comms/v4/proto/bff/%.gen.ts) +COMMS_TS = $(COMMS_PROTO_FILES:packages/shared/comms/v4/proto/%.proto=packages/shared/comms/v4/proto/%.gen.ts) + CWD = $(shell pwd) PROTOC = node_modules/.bin/protobuf/bin/protoc @@ -62,7 +68,7 @@ empty-parcels: cp $(EMPTY_SCENES)/mappings.json static/loader/empty-scenes/mappings.json cp -R $(EMPTY_SCENES)/contents static/loader/empty-scenes/contents -build-essentials: ${PBRENDERER_TS} ${PBS_TS} $(COMPILED_SUPPORT_JS_FILES) $(SCENE_SYSTEM) $(INTERNAL_SCENES) $(DECENTRALAND_LOADER) $(GIF_PROCESSOR) $(VOICE_CHAT_CODEC_WORKER) empty-parcels +build-essentials: ${BFF_TS} ${COMMS_TS} ${PBRENDERER_TS} ${PBS_TS} $(COMPILED_SUPPORT_JS_FILES) $(SCENE_SYSTEM) $(INTERNAL_SCENES) $(DECENTRALAND_LOADER) $(GIF_PROCESSOR) $(VOICE_CHAT_CODEC_WORKER) empty-parcels # Entry points static/%.js: build-essentials packages/entryPoints/%.ts @@ -202,6 +208,22 @@ packages/renderer-protocol/proto/%.gen.ts: node_modules/@dcl/protocol/renderer-p -I="$(PWD)/node_modules/@dcl/protocol/renderer-protocol/" \ "$(PWD)/node_modules/@dcl/protocol/renderer-protocol/$*.proto"; -compile_apis: ${PBS_TS} +packages/shared/comms/v4/proto/bff/%.gen.ts: packages/shared/comms/v4/proto/bff/%.proto node_modules/.bin/protobuf/bin/protoc + ${PROTOC} \ + --plugin=./node_modules/.bin/protoc-gen-ts_proto \ + --ts_proto_opt=esModuleInterop=true,returnObservable=false,outputServices=generic-definitions \ + --ts_proto_opt=fileSuffix=.gen \ + --ts_proto_out="$(PWD)/packages/shared/comms/v4/proto/bff" -I="$(PWD)/packages/shared/comms/v4/proto/bff" \ + "$(PWD)/packages/shared/comms/v4/proto/bff/$*.proto" + +packages/shared/comms/v4/proto/%.gen.ts: packages/shared/comms/v4/proto/%.proto node_modules/.bin/protobuf/bin/protoc + ${PROTOC} \ + --plugin=./node_modules/.bin/protoc-gen-ts_proto \ + --ts_proto_opt=esModuleInterop=true,oneof=unions\ + --ts_proto_opt=fileSuffix=.gen \ + --ts_proto_out="$(PWD)/packages/shared/comms/v4/proto" -I="$(PWD)/packages/shared/comms/v4/proto" \ + "$(PWD)/packages/shared/comms/v4/proto/$*.proto" + +compile_apis: ${BFF_TS} ${COMMS_TS} ${PBS_TS} compile_renderer_protocol: ${PBRENDERER_TS} diff --git a/package-lock.json b/package-lock.json index 332cffbbe..35787c0be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "license": "Apache-2.0", "dependencies": { "@dcl/catalyst-peer": "^1.0.4", + "@dcl/comms3-transports": "^1.0.0-20220701150400.commit-69b1b53", "@dcl/crypto": "^3.0.1", "@dcl/ecs-math": "^1.0.1", "@dcl/ecs-quests": "^1.3.1", @@ -393,6 +394,18 @@ "readable-stream": "^3.6.0" } }, + "node_modules/@dcl/comms3-transports": { + "version": "1.0.0-20220701150400.commit-69b1b53", + "resolved": "https://registry.npmjs.org/@dcl/comms3-transports/-/comms3-transports-1.0.0-20220701150400.commit-69b1b53.tgz", + "integrity": "sha512-5YMWrs4LY1dMSSFuk9Hgi8CnqGJs1Z9mD2ePkQ1DDai82JrGFiCtkrMKg7Tvq/k1/9m+u384TkvvD9/Xn61deA==", + "dependencies": { + "dotenv": "^16.0.1", + "fp-future": "^1.0.1", + "livekit-client": "^1.1.2", + "mz-observable": "^1.0.1", + "ts-proto": "^1.115.5" + } + }, "node_modules/@dcl/crypto": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@dcl/crypto/-/crypto-3.0.1.tgz", @@ -4219,6 +4232,14 @@ "npm": ">=1.2" } }, + "node_modules/dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -5023,9 +5044,9 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "engines": { "node": ">=0.8.x" } @@ -7090,6 +7111,19 @@ "node": ">= 0.8.0" } }, + "node_modules/livekit-client": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-1.1.7.tgz", + "integrity": "sha512-BtfA0yldGFvpj5uWov6kuo60O2UTxizeu3JYtlqGhEvPrmDVgxORdxC0N865B6bPsE2UhQA9g2ScHKccbjrCOg==", + "dependencies": { + "events": "^3.3.0", + "loglevel": "^1.8.0", + "protobufjs": "^6.11.2", + "ts-debounce": "^3.0.0", + "typed-emitter": "^2.1.0", + "webrtc-adapter": "^8.1.1" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -7260,9 +7294,9 @@ } }, "node_modules/loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", "engines": { "node": ">= 0.6.0" }, @@ -10056,7 +10090,7 @@ "version": "6.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", - "dev": true, + "devOptional": true, "dependencies": { "tslib": "^1.9.0" }, @@ -10129,6 +10163,11 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" }, + "node_modules/sdp": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.0.3.tgz", + "integrity": "sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A==" + }, "node_modules/sdp-transform": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", @@ -11138,6 +11177,11 @@ "node": ">=0.10.0" } }, + "node_modules/ts-debounce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-3.0.0.tgz", + "integrity": "sha512-7jiRWgN4/8IdvCxbIwnwg2W0bbYFBH6BxFqBjMKk442t7+liF2Z1H6AUCcl8e/pD93GjPru+axeiJwFmRww1WQ==" + }, "node_modules/ts-loader": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.2.tgz", @@ -11174,9 +11218,9 @@ } }, "node_modules/ts-proto": { - "version": "1.115.4", - "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.115.4.tgz", - "integrity": "sha512-q2FfWVpTNJRBMXglZH0wHMEbLOEuxkDuRtyk0j5POGm7oA1Btd9sHw6GzGs6DkfYfre/BCtLmMi4uOueJpBvCQ==", + "version": "1.115.5", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.115.5.tgz", + "integrity": "sha512-JGk6hln3JP0T0wJlqIOUGE/qHDYcI547hrJwAzeLq1E2l7IdYnYiAJRsvJEOsvotIyZXMbHNlZedIZEVIPAspQ==", "dependencies": { "@types/object-hash": "^1.3.0", "dataloader": "^1.4.0", @@ -11319,7 +11363,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true + "devOptional": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -11403,6 +11447,14 @@ "node": ">= 0.6" } }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "optionalDependencies": { + "rxjs": "*" + } + }, "node_modules/typed-url-params": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-url-params/-/typed-url-params-1.0.1.tgz", @@ -12267,6 +12319,18 @@ "node": ">=0.10.0" } }, + "node_modules/webrtc-adapter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz", + "integrity": "sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg==", + "dependencies": { + "sdp": "^3.0.2" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -12722,6 +12786,18 @@ } } }, + "@dcl/comms3-transports": { + "version": "1.0.0-20220701150400.commit-69b1b53", + "resolved": "https://registry.npmjs.org/@dcl/comms3-transports/-/comms3-transports-1.0.0-20220701150400.commit-69b1b53.tgz", + "integrity": "sha512-5YMWrs4LY1dMSSFuk9Hgi8CnqGJs1Z9mD2ePkQ1DDai82JrGFiCtkrMKg7Tvq/k1/9m+u384TkvvD9/Xn61deA==", + "requires": { + "dotenv": "^16.0.1", + "fp-future": "^1.0.1", + "livekit-client": "^1.1.2", + "mz-observable": "^1.0.1", + "ts-proto": "^1.115.5" + } + }, "@dcl/crypto": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@dcl/crypto/-/crypto-3.0.1.tgz", @@ -15825,6 +15901,11 @@ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, + "dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -16421,9 +16502,9 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "evp_bytestokey": { "version": "1.0.3", @@ -18071,6 +18152,19 @@ "type-check": "~0.3.2" } }, + "livekit-client": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-1.1.7.tgz", + "integrity": "sha512-BtfA0yldGFvpj5uWov6kuo60O2UTxizeu3JYtlqGhEvPrmDVgxORdxC0N865B6bPsE2UhQA9g2ScHKccbjrCOg==", + "requires": { + "events": "^3.3.0", + "loglevel": "^1.8.0", + "protobufjs": "^6.11.2", + "ts-debounce": "^3.0.0", + "typed-emitter": "^2.1.0", + "webrtc-adapter": "^8.1.1" + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -18204,9 +18298,9 @@ } }, "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==" }, "lolex": { "version": "2.7.5", @@ -20358,7 +20452,7 @@ "version": "6.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", - "dev": true, + "devOptional": true, "requires": { "tslib": "^1.9.0" } @@ -20421,6 +20515,11 @@ } } }, + "sdp": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.0.3.tgz", + "integrity": "sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A==" + }, "sdp-transform": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz", @@ -21249,6 +21348,11 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "ts-debounce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-3.0.0.tgz", + "integrity": "sha512-7jiRWgN4/8IdvCxbIwnwg2W0bbYFBH6BxFqBjMKk442t7+liF2Z1H6AUCcl8e/pD93GjPru+axeiJwFmRww1WQ==" + }, "ts-loader": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.2.tgz", @@ -21278,9 +21382,9 @@ } }, "ts-proto": { - "version": "1.115.4", - "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.115.4.tgz", - "integrity": "sha512-q2FfWVpTNJRBMXglZH0wHMEbLOEuxkDuRtyk0j5POGm7oA1Btd9sHw6GzGs6DkfYfre/BCtLmMi4uOueJpBvCQ==", + "version": "1.115.5", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.115.5.tgz", + "integrity": "sha512-JGk6hln3JP0T0wJlqIOUGE/qHDYcI547hrJwAzeLq1E2l7IdYnYiAJRsvJEOsvotIyZXMbHNlZedIZEVIPAspQ==", "requires": { "@types/object-hash": "^1.3.0", "dataloader": "^1.4.0", @@ -21392,7 +21496,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true + "devOptional": true }, "tsutils": { "version": "3.21.0", @@ -21452,6 +21556,14 @@ "mime-types": "~2.1.24" } }, + "typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "requires": { + "rxjs": "*" + } + }, "typed-url-params": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/typed-url-params/-/typed-url-params-1.0.1.tgz", @@ -22171,6 +22283,14 @@ } } }, + "webrtc-adapter": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz", + "integrity": "sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg==", + "requires": { + "sdp": "^3.0.2" + } + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index 49966a242..bbbaa285a 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "dependencies": { "@dcl/catalyst-peer": "^1.0.4", + "@dcl/comms3-transports": "^1.0.0-20220701150400.commit-69b1b53", "@dcl/crypto": "^3.0.1", "@dcl/ecs-math": "^1.0.1", "@dcl/ecs-quests": "^1.3.1", diff --git a/packages/shared/comms/context.ts b/packages/shared/comms/context.ts index 7145a8c3e..e7bd7fd9c 100644 --- a/packages/shared/comms/context.ts +++ b/packages/shared/comms/context.ts @@ -86,6 +86,7 @@ export class CommsContext { }, 5001) return true } catch (e: any) { + commsLogger.error(e) await this.disconnect() return false } diff --git a/packages/shared/comms/index.ts b/packages/shared/comms/index.ts index 5b811b0f4..4a3349bbf 100644 --- a/packages/shared/comms/index.ts +++ b/packages/shared/comms/index.ts @@ -15,12 +15,14 @@ import { commsLogger, CommsContext } from './context' import { getCurrentIdentity } from 'shared/session/selectors' import { getCommsContext } from './selectors' import { Realm } from 'shared/dao/types' -import { resolveCommsV3Urls } from './v3/resolver' +import { resolveCommsV3Urls, resolveCommsV4Urls } from './v3/resolver' +import { BFFConfig, BFFConnection } from './v4/BFFConnection' +import { InstanceConnection as V4InstanceConnection } from './v4/InstanceConnection' import { removePeerByUUID, removeMissingPeers } from './peers' import { lastPlayerPositionReport } from 'shared/world/positionThings' import { ProfileType } from 'shared/profiles/types' -export type CommsVersion = 'v1' | 'v2' | 'v3' +export type CommsVersion = 'v1' | 'v2' | 'v3' | 'v4' export type CommsMode = CommsV1Mode | CommsV2Mode export type CommsV1Mode = 'local' | 'remote' export type CommsV2Mode = 'p2p' | 'server' @@ -166,6 +168,24 @@ export async function connectComms(realm: Realm): Promise { break } + case 'v4': { + await ensureMetaConfigurationInitialized() + + const { wsUrl } = resolveCommsV4Urls(realm)! + const bffConfig: BFFConfig = { + getIdentity: () => getIdentity() as AuthIdentity + } + + commsLogger.log('Using BFF service: ', wsUrl) + const bff = new BFFConnection(wsUrl, bffConfig) + connection = new V4InstanceConnection(bff, { + onPeerLeft: (peerId: string) => { + commsLogger.info('Removing peer that left an island', peerId) + removePeerByUUID(peerId) + } + }) + break + } default: { throw new Error(`unrecognized comms mode "${COMMS}"`) } diff --git a/packages/shared/comms/v3/resolver.ts b/packages/shared/comms/v3/resolver.ts index 37ab8a7d3..ce6614f10 100644 --- a/packages/shared/comms/v3/resolver.ts +++ b/packages/shared/comms/v3/resolver.ts @@ -4,6 +4,10 @@ function normalizeUrl(url: string) { return url.replace(/^:\/\//, window.location.protocol + '//') } +function httpToWs(url: string) { + return url.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://') +} + // adds the currently used protocol to the given URL export function urlWithProtocol(urlOrHostname: string) { if (!urlOrHostname.startsWith('http://') && !urlOrHostname.startsWith('https://') && !urlOrHostname.startsWith('://')) @@ -15,14 +19,10 @@ export function urlWithProtocol(urlOrHostname: string) { export function resolveCommsV3Urls(realm: Realm): { pingUrl: string; wsUrl: string } | undefined { if (realm.protocol !== 'v3') return - function httpToWs(url: string) { - return url.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://') - } - let server = 'https://explorer-bff.decentraland.io' if (realm.hostname === 'local') { - server = 'http://0.0.0.0:5000' + server = 'http://127.0.0.1:5000' } else if (realm.hostname === 'remote') { server = 'https://explorer-bff.decentraland.io' } else { @@ -35,6 +35,24 @@ export function resolveCommsV3Urls(realm: Realm): { pingUrl: string; wsUrl: stri return { pingUrl, wsUrl } } +export function resolveCommsV4Urls(realm: Realm): { pingUrl: string; wsUrl: string } | undefined { + if (realm.protocol !== 'v4') return + + let server: string + if (realm.serverName === 'local') { + server = 'http://127.0.0.1:3000' + } else if (realm.hostname === 'remote') { + server = 'https://explorer-bff.decentraland.io' + } else { + server = (realm.hostname.match(/:\/\//) ? realm.hostname : 'https://' + realm.hostname) + '/bff' + } + + const pingUrl = `${server}/status` + const wsUrl = httpToWs(`${server}/rpc`) + + return { pingUrl, wsUrl } +} + export function realmToConnectionString(realm: Realm) { if (realm.protocol === 'v2' && realm.serverName !== realm.hostname && realm.serverName.match(/^[a-z]+$/i)) { return realm.serverName diff --git a/packages/shared/comms/v4/BFFConnection.ts b/packages/shared/comms/v4/BFFConnection.ts new file mode 100644 index 000000000..4c93b4711 --- /dev/null +++ b/packages/shared/comms/v4/BFFConnection.ts @@ -0,0 +1,194 @@ +import { ILogger, createLogger } from 'shared/logger' +import { Observable } from 'mz-observable' +import { AuthIdentity, Authenticator } from '@dcl/crypto' +import { createRpcClient, RpcClientPort, Transport } from '@dcl/rpc' +import { WebSocketTransport } from '@dcl/rpc/dist/transports/WebSocket' +import { loadService, RpcClientModule } from '@dcl/rpc/dist/codegen' +import { BffAuthenticationServiceDefinition, WelcomePeerInformation } from './proto/bff/authentication-service.gen' +import { CommsServiceDefinition } from './proto/bff/comms-service.gen' + +type CommsService = RpcClientModule + +export declare type BFFConfig = { + getIdentity: () => AuthIdentity +} + +export type TopicData = { + peerId: string + data: Uint8Array +} + +export type TopicListener = { + subscriptionId: number +} + +export class BFFConnection { + private logger: ILogger = createLogger('BFF: ') + + public onDisconnectObservable = new Observable() + public onTopicMessageObservable = new Observable() + + private wsTransport: Transport | null = null + + private sceneTopics = new Map() + + private commsService: CommsService | null = null + + constructor(private url: string, private config: BFFConfig) {} + + async connect(): Promise { + this.wsTransport = WebSocketTransport(new WebSocket(this.url, 'comms')) + this.wsTransport.on('close', async () => { + this.logger.log('transport closed') + this.disconnect() + }) + this.wsTransport.on('error', async () => { + this.logger.log('transport closed') + this.disconnect() + }) + const rpcClient = await createRpcClient(this.wsTransport) + const port = await rpcClient.createPort('kernel') + const peerId = await this.authenticate(port) + + this.commsService = loadService(port, CommsServiceDefinition) + this.logger.log('Connected') + + return peerId + } + + public async addPeerTopicListener( + topic: string, + handler: (data: Uint8Array, peerId: string) => Promise + ): Promise { + if (!this.commsService) { + throw new Error('BFF is not connected') + } + + const subscription = await this.commsService.subscribeToPeerMessages({ topic }) + + async function getAsyncMessages(commsService: CommsService) { + for await (const { payload, sender } of commsService.getPeerMessages(subscription)) { + await handler(payload, sender) + } + } + + getAsyncMessages(this.commsService).catch((err) => { + this.logger.error(`Peer topic handler error: ${err.toString()}`) + this.disconnect() + }) + + return subscription + } + + public async addSystemTopicListener( + topic: string, + handler: (data: Uint8Array) => Promise + ): Promise { + if (!this.commsService) { + throw new Error('BFF is not connected') + } + + const subscription = await this.commsService.subscribeToSystemMessages({ topic }) + + async function getAsyncMessages(commsService: CommsService) { + for await (const { payload } of commsService.getSystemMessages(subscription)) { + await handler(payload) + } + } + + getAsyncMessages(this.commsService).catch((err) => { + this.logger.error(`System topic handler error: ${err.toString()}`) + this.disconnect() + }) + + return subscription + } + + public async removePeerTopicListener({ subscriptionId }: TopicListener): Promise { + if (!this.commsService) { + throw new Error('BFF is not connected') + } + + await this.commsService.unsubscribeToPeerMessages({ subscriptionId }) + } + + public async removeSystemTopicListener({ subscriptionId }: TopicListener): Promise { + if (!this.commsService) { + throw new Error('BFF is not connected') + } + + await this.commsService.unsubscribeToSystemMessages({ subscriptionId }) + } + + public async publishToTopic(topic: string, payload: Uint8Array): Promise { + if (!this.commsService) { + throw new Error('BFF is not connected') + } + + await this.commsService.publishToTopic({ topic, payload }) + } + + // TODO: replace this method with a listener + public async setTopics(topics: string[]): Promise { + const newTopics = new Set(topics) + const topicsToRemove = new Set() + const topicsToAdd = new Set() + + newTopics.forEach((topic) => { + if (!this.sceneTopics.has(topic)) { + topicsToAdd.add(topic) + } + }) + for (const topic of this.sceneTopics.keys()) { + if (!newTopics.has(topic)) { + topicsToRemove.add(topic) + } + } + + topicsToRemove.forEach((topic) => { + const listener = this.sceneTopics.get(topic) + if (listener) { + this.removePeerTopicListener(listener).catch((err) => { + this.logger.error(`Error removing peer topic listener for ${topic}: ${err.toString()}`) + }) + } + this.sceneTopics.delete(topic) + }) + + topicsToAdd.forEach(async (topic) => { + const listener = await this.addPeerTopicListener(topic, this.onSceneMessage.bind(this)) + this.sceneTopics.set(topic, listener) + }) + } + + disconnect() { + if (this.wsTransport) { + this.wsTransport.close() + this.wsTransport = null + this.onDisconnectObservable.notifyObservers() + } + } + + private async authenticate(port: RpcClientPort): Promise { + const identity = this.config.getIdentity() + const address = identity.authChain[0].payload + + const auth = loadService(port, BffAuthenticationServiceDefinition) + + const getChallengeResponse = await auth.getChallenge({ address }) + if (getChallengeResponse.alreadyConnected) { + return address + } + + const authChainJson = JSON.stringify(Authenticator.signPayload(identity, getChallengeResponse.challengeToSign)) + const authResponse: WelcomePeerInformation = await auth.authenticate({ authChainJson }) + return authResponse.peerId + } + + private async onSceneMessage(data: Uint8Array, peerId: string) { + this.onTopicMessageObservable.notifyObservers({ + peerId, + data + }) + } +} diff --git a/packages/shared/comms/v4/InstanceConnection.ts b/packages/shared/comms/v4/InstanceConnection.ts new file mode 100644 index 000000000..82fa49b3e --- /dev/null +++ b/packages/shared/comms/v4/InstanceConnection.ts @@ -0,0 +1,433 @@ +/// +import { store } from 'shared/store/isolatedStore' +import { getCommsConfig } from 'shared/meta/selectors' +import { Position3D } from './types' +import { Data, Profile_ProfileType } from './proto/comms.gen' +import { Position } from '../../comms/interface/utils' +import { BFFConnection, TopicData, TopicListener } from './BFFConnection' +import { TransportsConfig, Transport, DummyTransport, TransportMessage, createTransport } from '@dcl/comms3-transports' +import { createLogger } from 'shared/logger' +import { lastPlayerPositionReport } from 'shared/world/positionThings' + +import { CommsEvents, RoomConnection } from '../../comms/interface/index' +import { ProfileType } from 'shared/profiles/types' +import { EncodedFrame } from 'voice-chat-codec/types' +import mitt from 'mitt' +import { Avatar } from '@dcl/schemas' +import { Reader } from 'protobufjs/minimal' +import { HeartbeatMessage, LeftIslandMessage, IslandChangedMessage } from './proto/archipelago.gen' + +export type Config = { + onPeerLeft: (peerId: string) => void +} + +export class InstanceConnection implements RoomConnection { + events = mitt() + + private logger = createLogger('CommsV4: ') + private transport: Transport = new DummyTransport() + private heartBeatInterval: any = null + private islandChangedListener: TopicListener | null = null + private peerLeftListener: TopicListener | null = null + private onPeerLeft: (peerId: string) => void + + constructor(private bff: BFFConnection, { onPeerLeft }: Config) { + this.onPeerLeft = onPeerLeft + this.bff.onTopicMessageObservable.add(this.handleTopicMessage.bind(this)) + this.bff.onDisconnectObservable.add(this.disconnect.bind(this)) + } + + async connect(): Promise { + const peerId = await this.bff.connect() + const commsConfig = getCommsConfig(store.getState()) + const config: TransportsConfig = { + logger: this.logger, + bff: this.bff, + selfPosition: this.selfPosition, + peerId, + p2p: { + verbose: true, + debugWebRtcEnabled: false + }, + livekit: { + verbose: false + }, + ws: { + verbose: false + } + } + + if (!commsConfig.relaySuspensionDisabled) { + config.p2p.relaySuspensionConfig = { + relaySuspensionInterval: commsConfig.relaySuspensionInterval ?? 750, + relaySuspensionDuration: commsConfig.relaySuspensionDuration ?? 5000 + } + } + + this.heartBeatInterval = setInterval(async () => { + const position = this.selfPosition() + if (position) { + const d = HeartbeatMessage.encode({ + position: { + x: position[0], + y: position[1], + z: position[2] + } + }).finish() + try { + await this.bff.publishToTopic('heartbeat', d) + } catch (err: any) { + this.logger.error(`Heartbeat failed ${err.toString()}`) + await this.disconnect() + } + } + }, 2000) + + this.islandChangedListener = await this.bff.addSystemTopicListener( + `${peerId}.island_changed`, + async (data: Uint8Array) => { + let islandChangedMessage: IslandChangedMessage + try { + islandChangedMessage = IslandChangedMessage.decode(Reader.create(data)) + } catch (e) { + this.logger.error('cannot process island change message', e) + return + } + + this.logger.log(`change island message ${islandChangedMessage.connStr}`) + const transport = createTransport(config, islandChangedMessage) + + if (this.peerLeftListener) { + await this.bff.removeSystemTopicListener(this.peerLeftListener) + } + this.peerLeftListener = await this.bff.addSystemTopicListener( + `island.${islandChangedMessage.islandId}.peer_left`, + async (data: Uint8Array) => { + try { + const peerLeftMessage = LeftIslandMessage.decode(Reader.create(data)) + this.onPeerLeft(peerLeftMessage.peerId) + } catch (e) { + this.logger.error('cannot process peer left message', e) + return + } + } + ) + + if (!transport) { + this.logger.error(`Invalid islandConnStr ${islandChangedMessage.connStr}`) + return + } + await this.changeTransport(transport) + } + ) + } + + async sendPositionMessage(p: Position) { + const d = Data.encode({ + message: { + $case: 'position', + position: { + time: Date.now(), + positionX: p[0], + positionY: p[1], + positionZ: p[2], + rotationX: p[3], + rotationY: p[4], + rotationZ: p[5], + rotationW: p[6] + } + } + }).finish() + + return this.transport.send(d, { reliable: false }) + } + + async sendParcelUpdateMessage(_: Position, _newPosition: Position) {} + + async sendProfileMessage(_: Position, __: string, profileType: ProfileType, version: number) { + const d = Data.encode({ + message: { + $case: 'profile', + profile: { + time: Date.now(), + profileType: profileType === ProfileType.LOCAL ? Profile_ProfileType.LOCAL : Profile_ProfileType.DEPLOYED, + profileVersion: `${version}` + } + } + }).finish() + + return this.transport.send(d, { reliable: true, identity: true }) + } + + async sendProfileRequest(_: Position, userId: string, version: number | undefined) { + const d = Data.encode({ + message: { + $case: 'profileRequest', + profileRequest: { + time: Date.now(), + userId: userId, + profileVersion: `${version}` + } + } + }).finish() + + return this.transport.send(d, { reliable: true, identity: true }) + } + + async sendProfileResponse(_: Position, profile: Avatar) { + const d = Data.encode({ + message: { + $case: 'profileResponse', + profileResponse: { + time: Date.now(), + serializedProfile: JSON.stringify(profile) + } + } + }).finish() + + return this.transport.send(d, { reliable: true, identity: true }) + } + + async sendInitialMessage(_: string, profileType: ProfileType) { + const d = Data.encode({ + message: { + $case: 'profile', + profile: { + time: Date.now(), + profileType: profileType === ProfileType.LOCAL ? Profile_ProfileType.LOCAL : Profile_ProfileType.DEPLOYED, + profileVersion: '' + } + } + }).finish() + + return this.transport.send(d, { reliable: true, identity: true }) + } + + async sendParcelSceneCommsMessage(sceneId: string, message: string) { + const d = Data.encode({ + message: { + $case: 'scene', + scene: { + time: Date.now(), + sceneId: sceneId, + data: message + } + } + }).finish() + + return this.bff.publishToTopic(sceneId, d) + } + + async sendChatMessage(_: Position, messageId: string, text: string) { + const d = Data.encode({ + message: { + $case: 'chat', + chat: { + time: Date.now(), + messageId, + text + } + } + }).finish() + return this.transport.send(d, { reliable: true }) + } + + async setTopics(topics: string[]) { + return this.bff.setTopics(topics) + } + + async disconnect(): Promise { + if (this.islandChangedListener) { + await this.bff.removeSystemTopicListener(this.islandChangedListener) + } + + if (this.peerLeftListener) { + await this.bff.removeSystemTopicListener(this.peerLeftListener) + } + + if (this.heartBeatInterval) { + clearInterval(this.heartBeatInterval) + } + + if (this.transport) { + await this.transport.disconnect() + } + return this.bff.disconnect() + } + + async sendVoiceMessage(_: Position, frame: EncodedFrame): Promise { + const d = Data.encode({ + message: { + $case: 'voice', + voice: { + encodedSamples: frame.encoded, + index: frame.index + } + } + }).finish() + + return this.transport.send(d, { reliable: true }) + } + + protected handleTopicMessage(message: TopicData) { + let data: Data + try { + data = Data.decode(Reader.create(message.data)) + } catch (e: any) { + this.logger.error(`cannot decode topic message data ${e.toString()}`) + return + } + + switch (data.message?.$case) { + case 'scene': { + const sceneData = data.message?.scene + + this.events.emit('sceneMessageBus', { + sender: message.peerId, + time: sceneData.time, + data: { + id: sceneData.sceneId, + text: sceneData.data + } + }) + break + } + default: { + this.logger.log(`Ignoring category ${data.message?.$case}`) + break + } + } + } + + protected handleTransportMessage({ peer, payload }: TransportMessage) { + let data: Data + try { + data = Data.decode(Reader.create(payload)) + } catch (e: any) { + this.logger.error(`cannot decode topic message data ${e.toString()}`) + return + } + + if (!data.message) { + this.logger.error(`Transport message has no content`) + return + } + + const { $case } = data.message + + switch ($case) { + case 'position': { + const { position } = data.message + this.events.emit('position', { + sender: peer, + time: position.time, + data: [ + position.positionX, + position.positionY, + position.positionZ, + position.rotationX, + position.rotationY, + position.rotationZ, + position.rotationW, + false + ] + }) + + this.transport.onPeerPositionChange(peer, [position.positionX, position.positionY, position.positionZ]) + break + } + case 'chat': { + const { time, messageId, text } = data.message.chat + + this.events.emit('chatMessage', { + sender: peer, + time: time, + data: { + id: messageId, + text: text + } + }) + break + } + case 'voice': { + const { encodedSamples, index } = data.message.voice + + this.events.emit('voiceMessage', { + sender: peer, + time: new Date().getTime(), + data: { + encoded: encodedSamples, + index + } + }) + break + } + case 'profile': { + const { time, profileVersion, profileType } = data.message.profile + this.events.emit('profileMessage', { + sender: peer, + time: time, + data: { + user: peer, + version: profileVersion, + type: profileType === Profile_ProfileType.LOCAL ? ProfileType.LOCAL : ProfileType.DEPLOYED + } // We use deployed as default because that way we can emulate the old behaviour + }) + break + } + case 'profileRequest': { + const { userId, time, profileVersion } = data.message.profileRequest + this.events.emit('profileRequest', { + sender: peer, + time: time, + data: { + userId: userId, + version: profileVersion + } + }) + break + } + case 'profileResponse': { + const { time, serializedProfile } = data.message.profileResponse + this.events.emit('profileResponse', { + sender: peer, + time: time, + data: { + profile: JSON.parse(serializedProfile) as Avatar + } + }) + break + } + default: { + this.logger.log(`Ignoring category ${$case}`) + break + } + } + } + + private async changeTransport(transport: Transport): Promise { + const oldTransport = this.transport + + await transport.connect() + + transport.onMessageObservable.add(this.handleTransportMessage.bind(this)) + transport.onDisconnectObservable.add(this.disconnect.bind(this)) + + this.transport = transport + + globalThis.__DEBUG_PEER = transport + + if (oldTransport) { + oldTransport.onMessageObservable.clear() + oldTransport.onDisconnectObservable.clear() + await oldTransport.disconnect() + } + } + + private selfPosition(): Position3D | undefined { + if (lastPlayerPositionReport) { + const { x, y, z } = lastPlayerPositionReport.position + return [x, y, z] + } + } +} diff --git a/packages/shared/comms/v4/proto/archipelago.proto b/packages/shared/comms/v4/proto/archipelago.proto new file mode 100644 index 000000000..96c2c60dc --- /dev/null +++ b/packages/shared/comms/v4/proto/archipelago.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package protocol; + +message Position3DMessage { + double x = 1; + double y = 2; + double z = 3; +} + +message HeartbeatMessage { + Position3DMessage position = 1; +} + +message IslandChangedMessage { + string island_id = 1; + string conn_str = 2; + optional string from_island_id = 3; + map peers = 4; +} + +message LeftIslandMessage { + string island_id = 1; + string peer_id = 2; +} + +message JoinIslandMessage { + string island_id = 1; + string peer_id = 2; +} diff --git a/packages/shared/comms/v4/proto/bff/authentication-service.proto b/packages/shared/comms/v4/proto/bff/authentication-service.proto new file mode 100644 index 000000000..35c2e7dfb --- /dev/null +++ b/packages/shared/comms/v4/proto/bff/authentication-service.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +message GetChallengeRequest { + string address = 1; +} + +message GetChallengeResponse { + string challenge_to_sign = 1; + bool already_connected = 2; +} + +message SignedChallenge { + string auth_chain_json = 1; +} + +message WelcomePeerInformation { + string peer_id = 1; +} + +service BffAuthenticationService { + rpc GetChallenge(GetChallengeRequest) returns (GetChallengeResponse) {} + rpc Authenticate(SignedChallenge) returns (WelcomePeerInformation) {} +} diff --git a/packages/shared/comms/v4/proto/bff/comms-service.proto b/packages/shared/comms/v4/proto/bff/comms-service.proto new file mode 100644 index 000000000..0f44b7d8c --- /dev/null +++ b/packages/shared/comms/v4/proto/bff/comms-service.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +message Subscription { + uint32 subscription_id = 1; +} + +message SubscriptionRequest { + string topic = 1; +} + +message PeerTopicSubscriptionResultElem { + bytes payload = 1; + string topic = 2; + string sender = 3; +} + +message SystemTopicSubscriptionResultElem { + bytes payload = 1; + string topic = 2; +} + +message PublishToTopicRequest { + string topic = 1; + bytes payload = 2; +} + +message PublishToTopicResult { + bool ok = 1; +} + +message UnsubscriptionResult { + bool ok = 1; +} + +service CommsService { + rpc SubscribeToPeerMessages(SubscriptionRequest) returns (Subscription) {} + rpc GetPeerMessages(Subscription) returns (stream PeerTopicSubscriptionResultElem) {} + rpc UnsubscribeToPeerMessages(Subscription) returns (UnsubscriptionResult) {} + + rpc SubscribeToSystemMessages(SubscriptionRequest) returns (Subscription) {} + rpc GetSystemMessages(Subscription) returns (stream SystemTopicSubscriptionResultElem) {} + rpc UnsubscribeToSystemMessages(Subscription) returns (UnsubscriptionResult) {} + + + // send a peer message to a topic + rpc PublishToTopic(PublishToTopicRequest) returns (PublishToTopicResult) {} +} diff --git a/packages/shared/comms/v4/proto/comms.proto b/packages/shared/comms/v4/proto/comms.proto new file mode 100644 index 000000000..da4191e69 --- /dev/null +++ b/packages/shared/comms/v4/proto/comms.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package protocol; + +message Position { + double time = 2; + float position_x = 3; + float position_y = 4; + float position_z = 5; + float rotation_x = 6; + float rotation_y = 7; + float rotation_z = 8; + float rotation_w = 9; +} + +message Profile { + double time = 2; + string profile_version = 3; + enum ProfileType { + LOCAL = 0; + DEPLOYED = 1; + } + ProfileType profile_type = 4; +} + +message ProfileRequest { + double time = 2; + string profile_version = 3; + string user_id = 4; +} + +message ProfileResponse { + double time = 2; + string serialized_profile = 3; +} + +message Chat { + double time = 2; + string message_id = 3; + string text = 4; +} + +message Scene { + double time = 2; + string scene_id = 3; + string data = 4; +} + +message Voice { + bytes encoded_samples = 2; + uint32 index = 3; +} + +message Data { + oneof message { + Position position = 1; + Profile profile = 2; + ProfileRequest profile_request = 3; + ProfileResponse profile_response = 4; + Chat chat = 5; + Scene scene = 6; + Voice voice = 7; + } +} diff --git a/packages/shared/comms/v4/types.ts b/packages/shared/comms/v4/types.ts new file mode 100644 index 000000000..ac5caf3fc --- /dev/null +++ b/packages/shared/comms/v4/types.ts @@ -0,0 +1 @@ +export type Position3D = [number, number, number] diff --git a/packages/shared/dao/sagas.ts b/packages/shared/dao/sagas.ts index 861e2817f..4068f3c31 100644 --- a/packages/shared/dao/sagas.ts +++ b/packages/shared/dao/sagas.ts @@ -41,7 +41,12 @@ import { SET_WORLD_CONTEXT } from 'shared/comms/actions' import { getCommsContext, getRealm } from 'shared/comms/selectors' import { store } from 'shared/store/isolatedStore' import { CatalystNode } from 'shared/types' -import { candidateToRealm, resolveCommsConnectionString, resolveCommsV3Urls } from 'shared/comms/v3/resolver' +import { + candidateToRealm, + resolveCommsConnectionString, + resolveCommsV3Urls, + resolveCommsV4Urls +} from 'shared/comms/v3/resolver' import { getCurrentIdentity } from 'shared/session/selectors' import { USER_AUTHENTIFIED } from 'shared/session/actions' @@ -230,6 +235,13 @@ export async function checkValidRealm(realm: Realm) { const { pingUrl } = resolveCommsV3Urls(realm)! const pingResult = await ping(pingUrl) + return pingResult.status === ServerConnectionStatus.OK + } else if (realm.protocol === 'v4') { + const { pingUrl } = resolveCommsV4Urls(realm)! + const pingResult = await ping(pingUrl) + if (pingResult.status !== ServerConnectionStatus.OK) { + commsLogger.warn(`ping failed for ${pingUrl}`) + } return pingResult.status === ServerConnectionStatus.OK } return false