diff --git a/.github/actions/before-build/action.yml b/.github/actions/before-build/action.yml index a0f74cbaa6..9dbb3e1a4d 100644 --- a/.github/actions/before-build/action.yml +++ b/.github/actions/before-build/action.yml @@ -36,5 +36,5 @@ runs: shell: bash - name: "Build the project" - run: lerna run build:prod --scope quiet + run: lerna run build:prod --scope @quiet/desktop shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1087841c9b..80a688c61e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,4 +2,11 @@ ### Pull Request Checklist - [ ] I have linked this PR to related GitHub issue. -- [ ] I have updated the CHANGELOG.md file with relevant changes (the file is located at the root of monorepo). \ No newline at end of file +- [ ] I have updated the CHANGELOG.md file with relevant changes (the file is located at the root of monorepo). + +### (Optional) Mobile checklist + +Please ensure you completed the following checks if you did any changes to the mobile package: + +- [ ] I have run e2e tests for mobile +- [ ] I have updated base screenshots for visual regression tests diff --git a/.github/workflows/waggle-tests.yml b/.github/workflows/backend-tests.yml similarity index 100% rename from .github/workflows/waggle-tests.yml rename to .github/workflows/backend-tests.yml diff --git a/.github/workflows/check-visual-regression.yml b/.github/workflows/check-desktop-visual-regression.yml similarity index 90% rename from .github/workflows/check-visual-regression.yml rename to .github/workflows/check-desktop-visual-regression.yml index 1edc35e5d6..d29e0efb8d 100644 --- a/.github/workflows/check-visual-regression.yml +++ b/.github/workflows/check-desktop-visual-regression.yml @@ -1,4 +1,4 @@ -name: Visual regressions +name: Desktop visual regressions on: pull_request: @@ -24,7 +24,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: "Publish to Chromatic" uses: chromaui/action@v1 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 863782b5e1..4b951fd4e0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -27,14 +27,13 @@ jobs: uses: ./.github/actions/setup-env if: ${{ runner.os != 'Windows' }} with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,@quiet/mobile,e2e-tests,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,@quiet/mobile,e2e-tests,backend-bundle" - - name: "Setup environment for Windows" uses: ./.github/actions/setup-env if: ${{ runner.os == 'Windows' }} with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,e2e-tests,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,e2e-tests,backend-bundle" - name: "Lint" if: ${{ runner.os != 'Windows' }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/desktop-build.yml similarity index 93% rename from .github/workflows/build-release.yml rename to .github/workflows/desktop-build.yml index cccdbde5a0..c3e0fcf512 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/desktop-build.yml @@ -1,4 +1,4 @@ -name: Build release +name: Desktop release build on: release: @@ -6,18 +6,27 @@ on: [released, prereleased] jobs: + run-e2e-tests-linux: + if: | + startsWith(github.ref, 'refs/tags/@quiet/desktop') uses: ./.github/workflows/e2e-linux.yml + run-e2e-tests-mac: + if: | + startsWith(github.ref, 'refs/tags/@quiet/desktop') uses: ./.github/workflows/e2e-mac.yml + run-e2e-tests-win: + if: | + startsWith(github.ref, 'refs/tags/@quiet/desktop') uses: ./.github/workflows/e2e-win.yml build-linux: # needs: run-e2e-tests-linux runs-on: ubuntu-22.04 if: | - startsWith(github.ref, 'refs/tags/quiet') + startsWith(github.ref, 'refs/tags/@quiet/desktop') env: TEST_MODE: ${{ github.event.action == 'prereleased' }} @@ -30,7 +39,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: Install libfuse run: sudo apt install libfuse2 @@ -52,7 +61,7 @@ jobs: run: cd packages/desktop && USE_HARD_LINKS=false node_modules/.bin/electron-builder -p always --linux ${{ env.ELECTRON_BUILDER_PROPS }} - name: "Calculate new checksum for electron updater" - run: lerna run postBuild --scope quiet + run: lerna run postBuild --scope @quiet/desktop - name: "Push electron-updater new checksum to S3" uses: vinkabuki/upload-s3-action@master @@ -89,7 +98,7 @@ jobs: # needs: run-e2e-tests-mac runs-on: macos-latest if: | - startsWith(github.ref, 'refs/tags/quiet') + startsWith(github.ref, 'refs/tags/@quiet/desktop') env: TEST_MODE: ${{ github.event.action == 'prereleased' }} @@ -111,7 +120,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: Before build uses: ./.github/actions/before-build @@ -162,7 +171,7 @@ jobs: # needs: run-e2e-tests-win runs-on: windows-2019 if: | - startsWith(github.ref, 'refs/tags/quiet') + startsWith(github.ref, 'refs/tags/@quiet/desktop') env: TEST_MODE: ${{ github.event.action == 'prereleased' }} @@ -189,7 +198,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle,e2e-tests" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle,e2e-tests" - name: Before build uses: ./.github/actions/before-build diff --git a/.github/workflows/desktop-rtl-tests.yml b/.github/workflows/desktop-rtl-tests.yml new file mode 100644 index 0000000000..8e8343b736 --- /dev/null +++ b/.github/workflows/desktop-rtl-tests.yml @@ -0,0 +1,31 @@ +name: Desktop - state-manager bracket tests (RTL) + +on: + pull_request: + paths: + - packages/desktop/** + - packages/state-manager/** + +jobs: + desktop-tests: + timeout-minutes: 25 + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-20.04, macos-latest] + + steps: + - name: "Print OS" + run: echo ${{ matrix.os }} + + - uses: actions/checkout@v3 + + - name: "Setup environment" + uses: ./.github/actions/setup-env + with: + cachePrefix: "desktop-tests" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" + + - name: "Desktop - state-manager bracket tests" + run: lerna run rtl-test --scope @quiet/desktop --stream diff --git a/.github/workflows/regression-test.yml b/.github/workflows/desktop-test-scroll.yml similarity index 93% rename from .github/workflows/regression-test.yml rename to .github/workflows/desktop-test-scroll.yml index 1eb554931e..6e59c8e3f7 100644 --- a/.github/workflows/regression-test.yml +++ b/.github/workflows/desktop-test-scroll.yml @@ -1,4 +1,4 @@ -name: Scroll regression tests +name: Desktop scroll regression tests on: pull_request: @@ -23,7 +23,7 @@ jobs: - name: Setup environment uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: "Install libs" run: sudo apt-get update && sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/desktop-tests.yml similarity index 75% rename from .github/workflows/frontend-tests.yml rename to .github/workflows/desktop-tests.yml index fbdbdf19d9..e5b21cfe10 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/desktop-tests.yml @@ -24,10 +24,7 @@ jobs: uses: ./.github/actions/setup-env with: cachePrefix: "desktop-tests" - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: "Unit tests" - run: lerna run test --scope quiet --stream - - - name: "desktop-state-manager bracket tests" - run: lerna run rtl-test --scope quiet --stream + run: lerna run test --scope @quiet/desktop --stream diff --git a/.github/workflows/e2e-crossplatform.yml b/.github/workflows/e2e-crossplatform.yml index 42f4065efb..b45d5caddb 100644 --- a/.github/workflows/e2e-crossplatform.yml +++ b/.github/workflows/e2e-crossplatform.yml @@ -1,6 +1,14 @@ name: E2E cross platform -on: [pull_request] +on: + pull_request: + paths: + - packages/desktop/** + - packages/backend/** + - packages/state-manager/** + - packages/identity/** + - packages/common/** + jobs: mac: uses: ./.github/workflows/e2e-mac.yml diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml new file mode 100644 index 0000000000..91c81888e1 --- /dev/null +++ b/.github/workflows/e2e-ios.yml @@ -0,0 +1,39 @@ +name: E2E iOS + +on: + pull_request: + paths: + - packages/mobile/** + +jobs: + detox: + timeout-minutes: 10 + runs-on: [self-hosted, macOS, ARM64] + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + npm i + npm run lerna bootstrap --scope @quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/mobile,backend-bundle + + - name: Pull binaries + run: | + git lfs install + git lfs pull + + - name: Install pods + run: | + cd packages/mobile/ios + pod install + + - name: Build Detox + run: | + cd packages/mobile + detox build -c ios.sim.debug.ci + + - name: Run basic tests + run: | + cd packages/mobile + detox test starter -c ios.sim.debug.ci diff --git a/.github/workflows/e2e-linux.yml b/.github/workflows/e2e-linux.yml index e2dd29f39a..adca0030d3 100644 --- a/.github/workflows/e2e-linux.yml +++ b/.github/workflows/e2e-linux.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/actions/setup-env with: cachePrefix: "e2e-crossplatform-linux" - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle,e2e-tests" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle,e2e-tests" - name: Run X11 run: | diff --git a/.github/workflows/e2e-mac.yml b/.github/workflows/e2e-mac.yml index 8e1b0c5e17..0bf9718477 100644 --- a/.github/workflows/e2e-mac.yml +++ b/.github/workflows/e2e-mac.yml @@ -17,7 +17,7 @@ jobs: uses: ./.github/actions/setup-env with: cachePrefix: "e2e-crossplatform-mac" - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle,e2e-tests" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle,e2e-tests" - name: Before build uses: ./.github/actions/before-build diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index 9f3fa52e37..9f06c84c28 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -19,7 +19,7 @@ jobs: uses: ./.github/actions/setup-env with: cachePrefix: "e2e-crossplatform-windows" - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle,e2e-tests" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle,e2e-tests" - name: "Fetch jsign" shell: bash diff --git a/.github/workflows/deploy-android.yaml b/.github/workflows/mobile-deploy-android.yaml similarity index 97% rename from .github/workflows/deploy-android.yaml rename to .github/workflows/mobile-deploy-android.yaml index 881fa11bb1..dd8cbd5878 100644 --- a/.github/workflows/deploy-android.yaml +++ b/.github/workflows/mobile-deploy-android.yaml @@ -1,4 +1,4 @@ -name: Deploy Android to Google Play (internal testing) +name: Deploy Android to Google Play on: release: @@ -13,7 +13,7 @@ jobs: strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest-m] steps: - name: "Print OS" diff --git a/.github/workflows/deploy-ios.yml b/.github/workflows/mobile-deploy-ios.yml similarity index 100% rename from .github/workflows/deploy-ios.yml rename to .github/workflows/mobile-deploy-ios.yml diff --git a/.github/workflows/nectar-tests.yml b/.github/workflows/state-manager-tests.yml similarity index 81% rename from .github/workflows/nectar-tests.yml rename to .github/workflows/state-manager-tests.yml index b5e3b966bb..389f2746e6 100644 --- a/.github/workflows/nectar-tests.yml +++ b/.github/workflows/state-manager-tests.yml @@ -23,10 +23,7 @@ jobs: - name: "Setup environment" uses: ./.github/actions/setup-env with: - bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,quiet,backend-bundle" + bootstrap-packages: "@quiet/eslint-config,@quiet/logger,@quiet/common,@quiet/types,@quiet/state-manager,@quiet/backend,@quiet/identity,@quiet/desktop,backend-bundle" - name: "Unit tests" run: lerna run test --scope @quiet/state-manager --stream - - - name: "desktop-state-manager bracket tests" - run: lerna run rtl-test --scope quiet --stream diff --git a/.gitignore b/.gitignore index fc30407b99..8b22c51fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules lerna-debug.log +c4/.structurizr +c4/workspace.json diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..72c7744b30 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.12.1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..5be4da8d35 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +[quiet@2.0.0-alpha.10] + +* Suspends certain websocket events until backend becomes fully operative (faster and dumber frontend). + +* Replaced greying out inputs with splash screen on joining/creating screens. + +* Fixes empty space between chat's input and a soft keyboard on iOS devices. + +* Changed registration process - user connects to the libp2p network directly instead of using registrar. Invitation link format changed. User csr is now saved to database. + +* Fixed android stucking on username registration screen introduced in previous alpha. + +* Added creator username to initial channel message. + +* Fixed bug with changing joining community/create community screens with required field. + +* Fixed bug with displaying incorrect default settings tab. + +* Replaced source of publicKey in sendMessage saga to CSR + +* Labels for unregistered and duplicate usernames with modals + +* Fixed LoadingPanel useEffect bug. + +* Use csrs instead of certificates as a source of user data + +* Integration state manager layer with UI layer(desktop and mobile) + +* Clarify autoupdate language in update modal to let users know that the app will update on restart. + +* C4 for Quiet architecture. Context and Container diagrams. + +* Invite tab as default in settings diff --git a/c4/README.md b/c4/README.md new file mode 100644 index 0000000000..084e027ea8 --- /dev/null +++ b/c4/README.md @@ -0,0 +1,28 @@ +# Quiet C4 Diagrams + +Quiet architecture presented on C4 diagrams. Currently it uses 2 diagrams - Context and Container. + +Made in Structurzir + +Current views saved in `/current` + +## Run locally + +1. Make sure you have Docker installed +2. Run command in terminal: ```docker pull structurizr/lite``` +3. Run docker container inside your Quiet directory and choosen porsts, example : ```docker run -it --rm -p 8080:8080 -v /__PATH_TO_REPOSITORY__/c4:/usr/local/structurizr structurizr/lite``` +4. Open `http://localhost:8080/` + +## Editing + +Everything is in file `workspace.dsl` + +After change, save file and reload page. + +Highly recommend extenstions for VSC: C4 DSL Extension and Structurizr. + +## Docs + +https://c4model.com/ + +https://docs.structurizr.com/ diff --git a/c4/current/C1-Context.png b/c4/current/C1-Context.png new file mode 100644 index 0000000000..3a57192bc3 Binary files /dev/null and b/c4/current/C1-Context.png differ diff --git a/c4/current/C2-Container.png b/c4/current/C2-Container.png new file mode 100644 index 0000000000..85113f55ad Binary files /dev/null and b/c4/current/C2-Container.png differ diff --git a/c4/workspace.dsl b/c4/workspace.dsl new file mode 100644 index 0000000000..e8c6056b9a --- /dev/null +++ b/c4/workspace.dsl @@ -0,0 +1,156 @@ +workspace { + + model { + + properties { + "structurizr.groupSeparator" "/" + } + + userA = person "User A" "Owner of Quiet community" + userB = person "User B" "Member of Quiet community" + userC = person "User C" "Member of Quiet community" + userD = person "User D" "Member of Quiet community" + + quietB = softwareSystem "Quiet App B" "Linux/MacOS/Windows/Android/iOS" { + userB -> this + } + + quietC = softwareSystem "Quiet App C" "Linux/MacOS/Windows/Android/iOS" { + userC -> this + } + + quietD = softwareSystem "Quiet App D" "Linux/MacOS/Windows/Android/iOS" { + userD -> this "One peer is enough for replicating all data" + } + + quietA = softwareSystem "Quiet App" "Linux/MacOS/Windows/Android/iOS" { + + desktops = group "Desktop Apps" { + + linux = container "Linux" "React & Electron" { + userA -> this + } + + macOS = container "MacOS" "React & Electron" { + userA -> this + } + + windows = container "Windows" "React & Electron" { + userA -> this + } + } + + mobiles = group "Mobiles" { + + android = container "Android" "UI layer" "React Native"{ + userA -> this + } + + iOS = container "iOS" "UI layer" "React Native"{ + userA -> this + } + + androidBackgroundWorker = group "Android Background Worker" { + + cpp = container "CPP" { + + } + + nodeJSAndroid = container "Node.js Android" { + android -> this + cpp -> this + } + + notifications = container "Notifications" "Java" { + nodeJSAndroid -> this "Conntected via WebSocket" + } + + } + + nodeJSiOS = container "Node.js iOS" { + iOS -> this + } + + + } + + stateManager = container "State Manager" "Redux Toolkit & Redux Saga" { + linux -> this + macOS -> this + windows -> this + android -> this + iOS -> this + } + + + backend = group "Backend" { + + nest = container "Nest JS" "Connection Manager as a core mediator between other services" { + stateManager -> this "Conntected via Socket IO" + nodeJSAndroid -> this + nodeJSiOS -> this + } + + reigstration = container "Registration Service" { + nest -> this + } + + levelDB = container "levelDB" "local database" { + nest -> this + } + + storage = group "Storage" { + + orbitDB = container "orbitDB" { + nest -> this + } + + ipfs = container "IPFS" { + orbitDB -> this + } + + libp2p = container "libp2p"{ + ipfs -> this + } + + } + + } + + tor = container "Tor"{ + nest -> this + } + + } + + + quietA -> quietB + quietA -> quietC + + quietB -> quietA + quietB -> quietC + + quietC -> quietA + quietC -> quietB + + quietD -> quietA + quietA -> quietD + + } + + + views { + systemLandscape { + include * + autolayout + } + + container quietA { + include * + autolayout + } + + + theme default + } +} \ No newline at end of file diff --git a/lerna.json b/lerna.json index 2501ad2db2..8a24301f53 100644 --- a/lerna.json +++ b/lerna.json @@ -5,7 +5,7 @@ "version": "independent", "command": { "version": { - "allowBranch": ["master", "develop", "workflows/require-e2e-before-build-release"], + "allowBranch": ["master", "develop", "mobile/releasable"], "conventionalCommits": true, "createRelease": "github" }, diff --git a/packages/backend-bundle/CHANGELOG.md b/packages/backend-bundle/CHANGELOG.md index c4416c4346..87d88e889b 100644 --- a/packages/backend-bundle/CHANGELOG.md +++ b/packages/backend-bundle/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0-alpha.18](https://github.com/ZbayApp/monorepo/compare/backend-bundle@1.6.1...backend-bundle@2.0.0-alpha.18) (2023-10-04) + +**Note:** Version bump only for package backend-bundle + + + + + +# [1.7.0-alpha.0](/compare/backend-bundle@1.6.0...backend-bundle@1.7.0-alpha.0) (2023-08-29) + +**Note:** Version bump only for package backend-bundle + + + + + ## [1.6.1](https://github.com/TryQuiet/quiet/compare/backend-bundle@1.6.0...backend-bundle@1.6.1) (2023-09-25) **Note:** Version bump only for package backend-bundle diff --git a/packages/backend-bundle/package-lock.json b/packages/backend-bundle/package-lock.json index 99bd405118..d7cafddce4 100644 --- a/packages/backend-bundle/package-lock.json +++ b/packages/backend-bundle/package-lock.json @@ -1,12 +1,12 @@ { "name": "backend-bundle", - "version": "1.6.1", + "version": "2.0.0-alpha.18", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "backend-bundle", - "version": "1.6.1", + "version": "2.0.0-alpha.18", "license": "ISC" } } diff --git a/packages/backend-bundle/package.json b/packages/backend-bundle/package.json index c5d2e277d6..c05b9449de 100644 --- a/packages/backend-bundle/package.json +++ b/packages/backend-bundle/package.json @@ -1,10 +1,11 @@ { "name": "backend-bundle", - "version": "1.6.1", + "version": "2.0.0-alpha.18", "description": "", "main": "bundle.cjs", "scripts": {}, "keywords": [], "author": "", - "license": "ISC" + "license": "ISC", + "private": true } diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 64e2156ca1..0ed5fe4960 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -3,6 +3,86 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0-alpha.20](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.19...@quiet/backend@2.0.0-alpha.20) (2023-10-09) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.19](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.18...@quiet/backend@2.0.0-alpha.19) (2023-10-04) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.18](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.4...@quiet/backend@2.0.0-alpha.18) (2023-10-04) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.5](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.4...@quiet/backend@2.0.0-alpha.5) (2023-09-19) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.4](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.1...@quiet/backend@2.0.0-alpha.4) (2023-09-18) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.3](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.2...@quiet/backend@2.0.0-alpha.3) (2023-09-14) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.2](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.1...@quiet/backend@2.0.0-alpha.2) (2023-09-06) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.1](https://github.com/TryQuiet/backend/compare/@quiet/backend@2.0.0-alpha.0...@quiet/backend@2.0.0-alpha.1) (2023-09-05) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [2.0.0-alpha.0](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.10.0-alpha.0...@quiet/backend@2.0.0-alpha.0) (2023-09-01) + +**Note:** Version bump only for package @quiet/backend + + + + + +# [1.10.0-alpha.0](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.0...@quiet/backend@1.10.0-alpha.0) (2023-08-29) + +**Note:** Version bump only for package @quiet/backend + + + + + ## [1.9.4](https://github.com/TryQuiet/backend/compare/@quiet/backend@1.9.3...@quiet/backend@1.9.4) (2023-10-03) **Note:** Version bump only for package @quiet/backend diff --git a/packages/backend/package-lock.json b/packages/backend/package-lock.json index 3ac5bdc09f..f31f587e56 100644 --- a/packages/backend/package-lock.json +++ b/packages/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@quiet/backend", - "version": "1.9.4", + "version": "2.0.0-alpha.20", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@quiet/backend", - "version": "1.9.4", + "version": "2.0.0-alpha.20", "license": "MIT", "dependencies": { "@chainsafe/libp2p-gossipsub": "6.1.0", @@ -6355,12 +6355,12 @@ } }, "node_modules/@types/orbit-db/node_modules/@ipld/dag-cbor": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.5.tgz", - "integrity": "sha512-TyqgtxEojc98rvxg4NGM+73JzQeM4+tK2VQes/in2mdyhO+1wbGuBijh1tvi9BErQ/dEblxs9v4vEQSX8mFCIw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.4.tgz", + "integrity": "sha512-HBNVngk/47pKNLTAelN6ORWgKkjJtQj96Xb+jIBtRShJGCsXgghj1TzTynTTIp1dZxwPe5rVIL6yjZmvdyP2Wg==", "dev": true, "dependencies": { - "cborg": "^4.0.0", + "cborg": "^2.0.1", "multiformats": "^12.0.1" }, "engines": { @@ -6369,9 +6369,9 @@ } }, "node_modules/@types/orbit-db/node_modules/@ipld/dag-cbor/node_modules/multiformats": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", - "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.0.tgz", + "integrity": "sha512-/qTOKKnU8nwcVURjRcS+UN0QYgdS5BPZzY10Aiciu2SqncyCVMGV8KtD83EBFmsuJDsSEmT4sGvzcTkCoMw0sQ==", "dev": true, "engines": { "node": ">=16.0.0", @@ -6406,12 +6406,12 @@ "license": "MIT" }, "node_modules/@types/orbit-db/node_modules/cborg": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.0.1.tgz", - "integrity": "sha512-jbQyS14n3d1W3QyK3WXLTzSrnJnpZphrr0PbxYpT2VXcTpzl6QQ5JeAMfRWvdqt/Vbsxp56OXe/RIYFl069LGQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-2.0.5.tgz", + "integrity": "sha512-xVW1rSIw1ZXbkwl2XhJ7o/jAv0vnVoQv/QlfQxV8a7V5PlA4UU/AcIiXqmpyybwNWy/GPQU1m/aBVNIWr7/T0w==", "dev": true, "bin": { - "cborg": "lib/bin.js" + "cborg": "cli.js" } }, "node_modules/@types/orbit-db/node_modules/long": { @@ -6431,8 +6431,8 @@ }, "node_modules/@types/orbit-db/node_modules/orbit-db": { "name": "@orbitdb/core", - "version": "0.31.0", - "resolved": "git+ssh://git@github.com/orbitdb/orbit-db.git#739103d66d3114052bdc8029f33feb746d1e510a", + "version": "0.30.1", + "resolved": "git+ssh://git@github.com/orbitdb/orbit-db.git#9bdd93c1bcb0a326e920c8272d91d2ef41063809", "dev": true, "license": "MIT", "dependencies": { @@ -6510,9 +6510,9 @@ } }, "node_modules/@types/orbit-db/node_modules/uint8arrays/node_modules/multiformats": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", - "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.0.tgz", + "integrity": "sha512-/qTOKKnU8nwcVURjRcS+UN0QYgdS5BPZzY10Aiciu2SqncyCVMGV8KtD83EBFmsuJDsSEmT4sGvzcTkCoMw0sQ==", "dev": true, "engines": { "node": ">=16.0.0", @@ -26752,19 +26752,19 @@ }, "dependencies": { "@ipld/dag-cbor": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.5.tgz", - "integrity": "sha512-TyqgtxEojc98rvxg4NGM+73JzQeM4+tK2VQes/in2mdyhO+1wbGuBijh1tvi9BErQ/dEblxs9v4vEQSX8mFCIw==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.0.4.tgz", + "integrity": "sha512-HBNVngk/47pKNLTAelN6ORWgKkjJtQj96Xb+jIBtRShJGCsXgghj1TzTynTTIp1dZxwPe5rVIL6yjZmvdyP2Wg==", "dev": true, "requires": { - "cborg": "^4.0.0", + "cborg": "^2.0.1", "multiformats": "^12.0.1" }, "dependencies": { "multiformats": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", - "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.0.tgz", + "integrity": "sha512-/qTOKKnU8nwcVURjRcS+UN0QYgdS5BPZzY10Aiciu2SqncyCVMGV8KtD83EBFmsuJDsSEmT4sGvzcTkCoMw0sQ==", "dev": true } } @@ -26792,9 +26792,9 @@ "from": "@types/ipfs@git+https://github.com/lukas2005/types-ipfs.git" }, "cborg": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-4.0.1.tgz", - "integrity": "sha512-jbQyS14n3d1W3QyK3WXLTzSrnJnpZphrr0PbxYpT2VXcTpzl6QQ5JeAMfRWvdqt/Vbsxp56OXe/RIYFl069LGQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-2.0.5.tgz", + "integrity": "sha512-xVW1rSIw1ZXbkwl2XhJ7o/jAv0vnVoQv/QlfQxV8a7V5PlA4UU/AcIiXqmpyybwNWy/GPQU1m/aBVNIWr7/T0w==", "dev": true }, "long": { @@ -26810,7 +26810,7 @@ "dev": true }, "orbit-db": { - "version": "git+ssh://git@github.com/orbitdb/orbit-db.git#739103d66d3114052bdc8029f33feb746d1e510a", + "version": "git+ssh://git@github.com/orbitdb/orbit-db.git#9bdd93c1bcb0a326e920c8272d91d2ef41063809", "dev": true, "from": "orbit-db@git+https://github.com/orbitdb/orbit-db.git", "requires": { @@ -26874,9 +26874,9 @@ }, "dependencies": { "multiformats": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz", - "integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.0.tgz", + "integrity": "sha512-/qTOKKnU8nwcVURjRcS+UN0QYgdS5BPZzY10Aiciu2SqncyCVMGV8KtD83EBFmsuJDsSEmT4sGvzcTkCoMw0sQ==", "dev": true } } diff --git a/packages/backend/package.json b/packages/backend/package.json index f24a835caf..91c069b28f 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,12 +1,13 @@ { "name": "@quiet/backend", - "version": "1.9.4", + "version": "2.0.0-alpha.20", "description": "tlg-manager", "types": "lib/index.d.ts", "type": "module", "exports": "lib/index.js", "author": "", "license": "MIT", + "private": true, "scripts": { "build": "tsc -p tsconfig.build.json", "webpack": "webpack --env mode=development && cp ./lib/bundle.cjs ../backend-bundle/bundle.cjs", @@ -53,8 +54,8 @@ "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", - "@quiet/eslint-config": "^1.3.0", - "@quiet/state-manager": "^1.9.1", + "@quiet/eslint-config": "^2.0.0-alpha.18", + "@quiet/state-manager": "^2.0.0-alpha.18", "@types/crypto-js": "^4.0.2", "@types/express": "^4.17.9", "@types/jest": "28.1.8", @@ -89,10 +90,10 @@ "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@peculiar/webcrypto": "1.4.3", - "@quiet/common": "^1.8.1", - "@quiet/identity": "^1.8.1", - "@quiet/logger": "^1.6.0", - "@quiet/types": "^1.8.1", + "@quiet/common": "^2.0.0-alpha.18", + "@quiet/identity": "^2.0.0-alpha.18", + "@quiet/logger": "^2.0.0-alpha.18", + "@quiet/types": "^2.0.0-alpha.18", "abortable-iterator": "^3.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.13.1", diff --git a/packages/backend/src/backendManager.ts b/packages/backend/src/backendManager.ts index 47fc4fcb84..9f3e9e21c6 100644 --- a/packages/backend/src/backendManager.ts +++ b/packages/backend/src/backendManager.ts @@ -5,6 +5,7 @@ import path from 'path' import getPort from 'get-port' import { AppModule } from './nest/app.module' import { ConnectionsManagerService } from './nest/connections-manager/connections-manager.service' +import { TorControl } from './nest/tor/tor-control.service' import { torBinForPlatform, torDirForPlatform } from './nest/common/utils' import initRnBridge from './rn-bridge' @@ -93,8 +94,7 @@ export const runBackendMobile = async (): Promise => { const rn_bridge = initRnBridge() - let app: INestApplicationContext - app = await NestFactory.createApplicationContext( + const app: INestApplicationContext = await NestFactory.createApplicationContext( AppModule.forOptions({ socketIOPort: options.dataPort, httpTunnelPort: options.httpTunnelPort ? options.httpTunnelPort : null, @@ -113,27 +113,13 @@ export const runBackendMobile = async (): Promise => { rn_bridge.channel.on('close', async () => { const connectionsManager = app.get(ConnectionsManagerService) - await connectionsManager.closeAllServices() - await app.close() + connectionsManager.closeSocket() }) rn_bridge.channel.on('open', async (msg: OpenServices) => { - app = await NestFactory.createApplicationContext( - AppModule.forOptions({ - socketIOPort: msg.socketIOPort, - httpTunnelPort: msg.httpTunnelPort ? msg.httpTunnelPort : null, - torAuthCookie: msg.authCookie ? msg.authCookie : null, - torControlPort: msg.torControlPort ? msg.torControlPort : await getPort(), - torBinaryPath: options.torBinary ? options.torBinary : null, - options: { - env: { - appDataPath: options.dataPath, - }, - createPaths: false, - }, - }), - { logger: ['warn', 'error', 'log', 'debug', 'verbose'] } - ) - console.log('started backend wiktor little bastard ') + const connectionsManager = app.get(ConnectionsManagerService) + const torControlParams = app.get(TorControl) + torControlParams.torControlParams.auth.value = msg.authCookie + await connectionsManager.openSocket() }) } diff --git a/packages/backend/src/nest/common/test.module.ts b/packages/backend/src/nest/common/test.module.ts index ba34c3c1e7..12e79c308c 100644 --- a/packages/backend/src/nest/common/test.module.ts +++ b/packages/backend/src/nest/common/test.module.ts @@ -14,6 +14,7 @@ import { SOCKS_PROXY_AGENT, DB_PATH, LEVEL_DB, + TEST_DATA_PORT, } from '../const' import { ConfigOptions } from '../types' import path from 'path' @@ -27,7 +28,7 @@ const libPath = torDirForPlatform() // torBinaryPath: '../../../../../3rd-party/tor/linux/tor', // torResourcesPath: '../../../../../3rd-party/tor/linux', export const defaultConfigForTest = { - socketIOPort: await getPort(), + socketIOPort: TEST_DATA_PORT, torBinaryPath: torBinForPlatform(), torResourcesPath: torPath, torControlPort: await getPort(), diff --git a/packages/backend/src/nest/common/utils.ts b/packages/backend/src/nest/common/utils.ts index d4b62b76c5..23d0e875ed 100644 --- a/packages/backend/src/nest/common/utils.ts +++ b/packages/backend/src/nest/common/utils.ts @@ -2,7 +2,7 @@ import fs from 'fs' import getPort from 'get-port' import path from 'path' import { Server } from 'socket.io' -import { User } from '@quiet/types' +import { UserData } from '@quiet/types' import createHttpsProxyAgent from 'https-proxy-agent' import PeerId from 'peer-id' import tmp from 'tmp' @@ -12,6 +12,7 @@ import { TestConfig } from '../const' import logger from './logger' import { createCertificatesTestHelper } from './client-server' import { Libp2pNodeParams } from '../libp2p/libp2p.types' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' const log = logger('test') export interface Ports { @@ -143,16 +144,8 @@ export const torDirForPlatform = (basePath?: string): string => { return torPath } -export const createLibp2pAddress = (address: string, peerId: string) => { - return `/dns4/${address}/tcp/443/wss/p2p/${peerId}` -} - -export const createLibp2pListenAddress = (address: string) => { - return `/dns4/${address}/tcp/443/wss` -} - -export const getUsersAddresses = async (users: User[]): Promise => { - const peers = users.map(async (userData: User) => { +export const getUsersAddresses = async (users: UserData[]): Promise => { + const peers = users.map(async (userData: UserData) => { return createLibp2pAddress(userData.onionAddress, userData.peerId) }) @@ -208,9 +201,6 @@ export const libp2pInstanceParams = async (): Promise => { listenAddresses: [createLibp2pListenAddress('localhost')], agent: createHttpsProxyAgent({ port: 1234, host: 'localhost' }), localAddress: createLibp2pAddress('localhost', peerId.toString()), - cert: pems.userCert, - key: pems.userKey, - ca: [pems.ca], targetPort: port, peers: [remoteAddress], } diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts index 2bd278647b..abc3ad923e 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.spec.ts @@ -1,34 +1,23 @@ +import { jest } from '@jest/globals' +import { LazyModuleLoader } from '@nestjs/core' import { Test, TestingModule } from '@nestjs/testing' +import { getFactory, prepareStore, type Store, type communities, type identity } from '@quiet/state-manager' +import { type Community, type Identity, type InitCommunityPayload, type LaunchRegistrarPayload } from '@quiet/types' +import { type FactoryGirl } from 'factory-girl' +import PeerId from 'peer-id' import { TestModule } from '../common/test.module' +import { libp2pInstanceParams, removeFilesFromDir } from '../common/utils' import { QUIET_DIR, TOR_PASSWORD_PROVIDER } from '../const' -import { ConnectionsManagerModule } from './connections-manager.module' -import { ConnectionsManagerService } from './connections-manager.service' -import PeerId from 'peer-id' -import { libp2pInstanceParams } from '../common/utils' -import { jest } from '@jest/globals' -import { CustomEvent } from '@libp2p/interfaces/events' -import { type communities, getFactory, prepareStore, type identity, type Store } from '@quiet/state-manager' -import { type FactoryGirl } from 'factory-girl' -import { DateTime } from 'luxon' -import waitForExpect from 'wait-for-expect' -import { - type Community, - type Identity, - type InitCommunityPayload, - type LaunchRegistrarPayload, - type NetworkStats, -} from '@quiet/types' -import { LocalDBKeys } from '../local-db/local-db.types' +import { Libp2pModule } from '../libp2p/libp2p.module' +import { Libp2pService } from '../libp2p/libp2p.service' import { LocalDbModule } from '../local-db/local-db.module' import { LocalDbService } from '../local-db/local-db.service' -import { RegistrationService } from '../registration/registration.service' +import { LocalDBKeys } from '../local-db/local-db.types' import { RegistrationModule } from '../registration/registration.module' -import { LazyModuleLoader } from '@nestjs/core' -import { Libp2pService } from '../libp2p/libp2p.service' -import { Libp2pModule } from '../libp2p/libp2p.module' +import { RegistrationService } from '../registration/registration.service' import { SocketModule } from '../socket/socket.module' -import { removeFilesFromDir } from '../common/utils' -import { Libp2pEvents } from '../libp2p/libp2p.types' +import { ConnectionsManagerModule } from './connections-manager.module' +import { ConnectionsManagerService } from './connections-manager.service' describe('ConnectionsManagerService', () => { let module: TestingModule @@ -126,7 +115,7 @@ describe('ConnectionsManagerService', () => { expect(launchCommunitySpy).toHaveBeenCalledWith(launchCommunityPayload) }) - it('launches community and registrar on init if their data exists in local db', async () => { + it('launches community on init if their data exists in local db', async () => { const launchCommunityPayload: InitCommunityPayload = { id: community.id, peerId: userIdentity.peerId, @@ -141,20 +130,9 @@ describe('ConnectionsManagerService', () => { peers: community.peerList, } - const launchRegistrarPayload: LaunchRegistrarPayload = { - id: community.id, - peerId: userIdentity.peerId.id, - // @ts-expect-error - rootCertString: community.CA?.rootCertString, - // @ts-expect-error - rootKeyString: community.CA?.rootKeyString, - privateKey: 'privateKey', - } - await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - await localDbService.put(LocalDBKeys.REGISTRAR, launchRegistrarPayload) - const peerAddress = '/dns4/test.onion/tcp/443/wss/p2p/peerid' + const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' await localDbService.put(LocalDBKeys.PEERS, { [peerAddress]: { peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', @@ -166,21 +144,17 @@ describe('ConnectionsManagerService', () => { await connectionsManagerService.closeAllServices() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity').mockResolvedValue() - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar').mockResolvedValue() await connectionsManagerService.init() expect(launchCommunitySpy).toHaveBeenCalledWith(Object.assign(launchCommunityPayload, { peers: [peerAddress] })) - expect(launchRegistrarSpy).toHaveBeenCalledWith(launchRegistrarPayload) }) it('does not launch community on init if its data does not exist in local db', async () => { await connectionsManagerService.closeAllServices() await connectionsManagerService.init() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity') - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar') expect(launchCommunitySpy).not.toHaveBeenCalled() - expect(launchRegistrarSpy).not.toHaveBeenCalled() }) // At this moment, that test have to be skipped, because checking statues is called before launchCommunity method @@ -210,7 +184,7 @@ describe('ConnectionsManagerService', () => { expect(launchSpy).toBeCalledTimes(1) }) - it('Bug reproduction - Error on startup - Error: TOR: Connection already established - Trigger launchCommunity and launchRegistrar from backend and state manager', async () => { + it('Bug reproduction - Error on startup - Error: TOR: Connection already established - Trigger launchCommunity from backend and state manager', async () => { const launchCommunityPayload: InitCommunityPayload = { id: community.id, peerId: userIdentity.peerId, @@ -225,21 +199,10 @@ describe('ConnectionsManagerService', () => { peers: community.peerList, } - const launchRegistrarPayload: LaunchRegistrarPayload = { - id: community.id, - peerId: userIdentity.peerId.id, - // @ts-expect-error - rootCertString: community.CA?.rootCertString, - // @ts-expect-error - rootKeyString: community.CA?.rootKeyString, - privateKey: '', - } - // await connectionsManager.init() await localDbService.put(LocalDBKeys.COMMUNITY, launchCommunityPayload) - await localDbService.put(LocalDBKeys.REGISTRAR, launchRegistrarPayload) - const peerAddress = '/dns4/test.onion/tcp/443/wss/p2p/peerid' + const peerAddress = '/dns4/test.onion/tcp/80/ws/p2p/peerid' await localDbService.put(LocalDBKeys.PEERS, { [peerAddress]: { peerId: 'QmaEvCkpUG7GxhgvMkk8wxurfi1ehjHhSUNRksWTmXN2ix', @@ -251,11 +214,9 @@ describe('ConnectionsManagerService', () => { await connectionsManagerService.closeAllServices() const launchCommunitySpy = jest.spyOn(connectionsManagerService, 'launchCommunity').mockResolvedValue() - const launchRegistrarSpy = jest.spyOn(registrationService, 'launchRegistrar').mockResolvedValue() await connectionsManagerService.init() expect(launchCommunitySpy).toBeCalledTimes(1) - expect(launchRegistrarSpy).toBeCalledTimes(1) }) }) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts index e88e7151b2..8c759f8571 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.tor.spec.ts @@ -4,14 +4,7 @@ import crypto from 'crypto' import { CustomEvent } from '@libp2p/interfaces/events' import { jest, beforeEach, describe, it, expect, afterEach } from '@jest/globals' import { communities, getFactory, identity, prepareStore, Store } from '@quiet/state-manager' -import { - createLibp2pAddress, - createPeerId, - createTmpDir, - libp2pInstanceParams, - removeFilesFromDir, - tmpQuietDirPath, -} from '../common/utils' +import { createPeerId, createTmpDir, libp2pInstanceParams, removeFilesFromDir, tmpQuietDirPath } from '../common/utils' import { NetworkStats, type Community, type Identity, type InitCommunityPayload } from '@quiet/types' import { LazyModuleLoader } from '@nestjs/core' @@ -37,6 +30,7 @@ import { DateTime } from 'luxon' import waitForExpect from 'wait-for-expect' import { Libp2pEvents } from '../libp2p/libp2p.types' import { sleep } from '../common/sleep' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(100_000) diff --git a/packages/backend/src/nest/connections-manager/connections-manager.service.ts b/packages/backend/src/nest/connections-manager/connections-manager.service.ts index 571d5bcaf1..d3ca9704aa 100644 --- a/packages/backend/src/nest/connections-manager/connections-manager.service.ts +++ b/packages/backend/src/nest/connections-manager/connections-manager.service.ts @@ -42,6 +42,10 @@ import { StorePeerListPayload, UploadFilePayload, PeerId as PeerIdType, + SaveCSRPayload, + SendUserCertificatePayload, + CommunityMetadata, + CommunityMetadataPayload, } from '@quiet/types' import { CONFIG_OPTIONS, QUIET_DIR, SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ConfigOptions, GetPorts, ServerIoProviderTypes } from '../types' @@ -165,17 +169,9 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.communityState)) return this.communityState = ServiceState.LAUNCHING } - const registrarData: LaunchRegistrarPayload = await this.localDbService.get(LocalDBKeys.REGISTRAR) - if (registrarData) { - if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return - this.registrarState = ServiceState.LAUNCHING - } if (community) { await this.launchCommunity(community) } - if (registrarData) { - await this.registrationService.launchRegistrar(registrarData) - } } public async closeAllServices(options: { saveTor: boolean } = { saveTor: false }) { @@ -207,6 +203,15 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } } + public closeSocket() { + this.serverIoProvider.io.close() + } + + // This method is only used on iOS through rn-bridge for reacting on lifecycle changes + public async openSocket() { + await this.socketService.init() + } + public async leaveCommunity() { this.tor.resetHiddenServices() this.serverIoProvider.io.close() @@ -273,7 +278,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI community: { ...community, privateKey: network2.hiddenService.privateKey, - registrarUrl: community.registrarUrl || network2.hiddenService.onionAddress.split('.')[0], + registrarUrl: community.registrarUrl || network2.hiddenService.onionAddress.split('.')[0], // TODO: remove }, network, } @@ -281,12 +286,14 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } public async createCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.createCommunity peers:', payload.peers) await this.launchCommunity(payload) this.logger(`Created and launched community ${payload.id}`) this.serverIoProvider.io.emit(SocketActionTypes.NEW_COMMUNITY, { id: payload.id }) } public async launchCommunity(payload: InitCommunityPayload) { + console.log('ConnectionsManager.launchCommunity peers:', payload.peers) this.communityState = ServiceState.LAUNCHING const communityData: InitCommunityPayload = await this.localDbService.get(LocalDBKeys.COMMUNITY) if (!communityData) { @@ -306,9 +313,17 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI } this.logger(`Launched community ${payload.id}`) + this.serverIoProvider.io.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.LAUNCHED_COMMUNITY) + this.communityId = payload.id this.communityState = ServiceState.LAUNCHED + + console.log('Hunting for heisenbug: Backend initialized community and sent event to state manager') + + // Unblock websocket endpoints + this.socketService.resolveReadyness() + this.serverIoProvider.io.emit(SocketActionTypes.COMMUNITY, { id: payload.id }) } public async launch(payload: InitCommunityPayload) { @@ -326,7 +341,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI const { Libp2pModule } = await import('../libp2p/libp2p.module') const moduleRef = await this.lazyModuleLoader.load(() => Libp2pModule) - this.logger('launchCommunityFromStorage') const { Libp2pService } = await import('../libp2p/libp2p.service') const lazyService = moduleRef.get(Libp2pService) this.libp2pService = lazyService @@ -335,6 +349,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI const _peerId = await peerIdFromKeys(restoredRsa.marshalPubKey(), restoredRsa.marshalPrivKey()) let peers = payload.peers + console.log(`Launching community ${payload.id}, payload peers: ${peers}`) if (!peers || peers.length === 0) { peers = [this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString())] } @@ -343,9 +358,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI peerId: _peerId, listenAddresses: [this.libp2pService.createLibp2pListenAddress(onionAddress)], agent: this.socksProxyAgent, - cert: payload.certs.certificate, - key: payload.certs.key, - ca: payload.certs.CA, localAddress: this.libp2pService.createLibp2pAddress(onionAddress, _peerId.toString()), targetPort: this.ports.libp2pHiddenService, peers, @@ -407,9 +419,6 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.registrationService.on(RegistrationEvents.ERROR, payload => { emitError(this.serverIoProvider.io, payload) }) - this.registrationService.on(SocketActionTypes.SEND_USER_CERTIFICATE, payload => { - this.serverIoProvider.io.emit(SocketActionTypes.SEND_USER_CERTIFICATE, payload) - }) this.registrationService.on(RegistrationEvents.NEW_USER, async payload => { await this.storageService?.saveCertificate(payload) }) @@ -422,6 +431,7 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.socketService.on(SocketActionTypes.CONNECTION, async () => { // Update Frontend with Initialized Communities if (this.communityId) { + console.log('Hunting for heisenbug: Backend initialized community and sent event to state manager') this.serverIoProvider.io.emit(SocketActionTypes.COMMUNITY, { id: this.communityId }) console.log('this.libp2pService.connectedPeers', this.libp2pService.connectedPeers) console.log('this.libp2pservice', this.libp2pService) @@ -445,38 +455,21 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI this.communityState = ServiceState.LAUNCHING await this.launchCommunity(args) }) - // Registration this.socketService.on(SocketActionTypes.LAUNCH_REGISTRAR, async (args: LaunchRegistrarPayload) => { + // Event left for setting permsData purposes this.logger(`socketService - ${SocketActionTypes.LAUNCH_REGISTRAR}`) - - const communityData = await this.localDbService.get(LocalDBKeys.REGISTRAR) - if (!communityData) { - await this.localDbService.put(LocalDBKeys.REGISTRAR, args) - } - console.log('this.registrarState', this.registrarState) - if ([ServiceState.LAUNCHING, ServiceState.LAUNCHED].includes(this.registrarState)) return - this.registrarState = ServiceState.LAUNCHING - await this.registrationService.launchRegistrar(args) - }) - this.socketService.on(SocketActionTypes.SAVED_OWNER_CERTIFICATE, async (args: SaveOwnerCertificatePayload) => { - const saveCertificatePayload: SaveCertificatePayload = { - certificate: args.certificate, - rootPermsData: args.permsData, + this.registrationService.permsData = { + certificate: args.rootCertString, + privKey: args.rootKeyString, } - await this.storageService?.saveCertificate(saveCertificatePayload) - }) - this.socketService.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (args: RegisterUserCertificatePayload) => { - // if (!this.socksProxyAgent) { - // this.createAgent() - // } - - await this.registrationService.sendCertificateRegistrationRequest( - args.serviceAddress, - args.userCsr, - args.communityId, - 120_000, - this.socksProxyAgent - ) + }) + this.socketService.on(SocketActionTypes.SEND_COMMUNITY_METADATA, async (payload: CommunityMetadata) => { + await this.storageService?.updateCommunityMetadata(payload) + }) + this.socketService.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => { + console.log(`On ${SocketActionTypes.SAVE_USER_CSR}`) + await this.storageService?.saveCSR(payload) + this.serverIoProvider.io.emit(SocketActionTypes.SAVED_USER_CSR, payload) }) this.socketService.on( SocketActionTypes.REGISTER_OWNER_CERTIFICATE, @@ -591,5 +584,18 @@ export class ConnectionsManagerService extends EventEmitter implements OnModuleI console.log('emitting deleted channel event back to state manager') this.serverIoProvider.io.emit(SocketActionTypes.CHANNEL_DELETION_RESPONSE, payload) }) + this.storageService.on(StorageEvents.REPLICATED_CSR, async (payload: string[]) => { + console.log(`On ${StorageEvents.REPLICATED_CSR}`) + this.serverIoProvider.io.emit(SocketActionTypes.RESPONSE_GET_CSRS, { csrs: payload }) + payload.forEach(csr => this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, csr)) + }) + this.storageService.on(StorageEvents.REPLICATED_COMMUNITY_METADATA, (payload: CommunityMetadata) => { + console.log(`On ${StorageEvents.REPLICATED_COMMUNITY_METADATA}: ${payload}`) + const communityMetadataPayload: CommunityMetadataPayload = { + rootCa: payload.rootCa, + ownerCertificate: payload.ownerCertificate, + } + this.serverIoProvider.io.emit(SocketActionTypes.SAVE_COMMUNITY_METADATA, communityMetadataPayload) + }) } } diff --git a/packages/backend/src/nest/const.ts b/packages/backend/src/nest/const.ts index ddb27b590c..f4c86c3a35 100644 --- a/packages/backend/src/nest/const.ts +++ b/packages/backend/src/nest/const.ts @@ -15,6 +15,8 @@ export enum TestConfig { IPFS_REPO_PATH = 'Ipfs-test-nest-backend', } +export const TEST_DATA_PORT = '9004' + export const QUIET_DIR_PATH = path.join(os.homedir(), Config.QUIET_DIR) export const TEST_QUIET_DIR_PATH = path.join(os.homedir(), TestConfig.QUIET_DIR) diff --git a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts index c94c787d30..6775f9a2e2 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.spec.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.spec.ts @@ -38,13 +38,11 @@ describe('Libp2pService', () => { it('creates libp2p address with proper ws type (%s)', async () => { const libp2pAddress = libp2pService.createLibp2pAddress(params.localAddress, params.peerId.toString()) - expect(libp2pAddress).toStrictEqual( - `/dns4/${params.localAddress}.onion/tcp/443/wss/p2p/${params.peerId.toString()}` - ) + expect(libp2pAddress).toStrictEqual(`/dns4/${params.localAddress}.onion/tcp/80/ws/p2p/${params.peerId.toString()}`) }) it('creates libp2p listen address', async () => { const libp2pListenAddress = libp2pService.createLibp2pListenAddress('onionAddress') - expect(libp2pListenAddress).toStrictEqual(`/dns4/onionAddress.onion/tcp/443/wss`) + expect(libp2pListenAddress).toStrictEqual(`/dns4/onionAddress.onion/tcp/80/ws`) }) }) diff --git a/packages/backend/src/nest/libp2p/libp2p.service.ts b/packages/backend/src/nest/libp2p/libp2p.service.ts index f35a3b6893..311b8f2b47 100644 --- a/packages/backend/src/nest/libp2p/libp2p.service.ts +++ b/packages/backend/src/nest/libp2p/libp2p.service.ts @@ -15,10 +15,10 @@ import { multiaddr } from '@multiformats/multiaddr' import { ConnectionProcessInfo, PeerId, SocketActionTypes } from '@quiet/types' import { SERVER_IO_PROVIDER, SOCKS_PROXY_AGENT } from '../const' import { ServerIoProviderTypes } from '../types' -import { createLibp2pListenAddress, createLibp2pAddress } from './libp2p.utils' import Logger from '../common/logger' import { webSockets } from '../websocketOverTor' import { all } from '../websocketOverTor/filters' +import { createLibp2pAddress, createLibp2pListenAddress } from '@quiet/common' @Injectable() export class Libp2pService extends EventEmitter { @@ -44,7 +44,7 @@ export class Libp2pService extends EventEmitter { return createLibp2pListenAddress(address) } - public async createInstance(params: Libp2pNodeParams): Promise { + public async createInstance(params: Libp2pNodeParams): Promise { if (this.libp2pInstance) { return this.libp2pInstance } @@ -78,9 +78,6 @@ export class Libp2pService extends EventEmitter { filter: all, websocket: { agent: params.agent, - cert: params.cert, - key: params.key, - ca: params.ca, }, localAddress: params.localAddress, targetPort: params.targetPort, @@ -121,6 +118,7 @@ export class Libp2pService extends EventEmitter { dialInChunks.stop() this.connectedPeers.set(remotePeerId, DateTime.utc().valueOf()) + this.logger(`${this.connectedPeers.size} connected peers`) this.emit(Libp2pEvents.PEER_CONNECTED, { peers: [remotePeerId], @@ -147,6 +145,7 @@ export class Libp2pService extends EventEmitter { const connectionDuration: number = connectionEndTime - connectionStartTime this.connectedPeers.delete(remotePeerId) + this.logger(`${this.connectedPeers.size} connected peers`) this.emit(Libp2pEvents.PEER_DISCONNECTED, { peer: remotePeerId, diff --git a/packages/backend/src/nest/libp2p/libp2p.types.ts b/packages/backend/src/nest/libp2p/libp2p.types.ts index a912015c23..d32cc0230f 100644 --- a/packages/backend/src/nest/libp2p/libp2p.types.ts +++ b/packages/backend/src/nest/libp2p/libp2p.types.ts @@ -11,9 +11,6 @@ export interface Libp2pNodeParams { peerId: any listenAddresses: string[] agent: Agent - cert: string - key: string - ca: string[] localAddress: string targetPort: number peers: string[] diff --git a/packages/backend/src/nest/local-db/local-db.service.spec.ts b/packages/backend/src/nest/local-db/local-db.service.spec.ts index 0ac06cba05..0cf4af79cf 100644 --- a/packages/backend/src/nest/local-db/local-db.service.spec.ts +++ b/packages/backend/src/nest/local-db/local-db.service.spec.ts @@ -73,7 +73,7 @@ describe('LocalDbService', () => { it('get sorted peers', async () => { const extraPeers = [ - '/dns4/zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion/tcp/443/wss/p2p/QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6', + '/dns4/zl37gnntp64dhnisddftypxbt5cqx6cum65vdv6oeaffrbqmemwc52ad.onion/tcp/443/ws/p2p/QmPGdGDUV1PXaJky4V53KSvFszdqEcM7KCoDpF2uFPf5w6', ] await localDbService.put(LocalDBKeys.PEERS, { ...peer1Stats, diff --git a/packages/backend/src/nest/registration/registration.functions.ts b/packages/backend/src/nest/registration/registration.functions.ts index 178682dd23..43bde69f62 100644 --- a/packages/backend/src/nest/registration/registration.functions.ts +++ b/packages/backend/src/nest/registration/registration.functions.ts @@ -20,8 +20,8 @@ import { PermsData, SocketActionTypes, SuccessfullRegistrarionResponse, - User, UserCertificatePayload, + UserData, } from '@quiet/types' import { CsrContainsFields, IsCsr } from './registration.validators' import { RegistrationEvents } from './registration.types' @@ -252,7 +252,7 @@ export const registerUser = async ( } } - const allUsers: User[] = [] + const allUsers: UserData[] = [] for (const cert of certificates) { const parsedCert = parseCertificate(cert) const onionAddress = getCertFieldValue(parsedCert, CertFieldsTypes.commonName) diff --git a/packages/backend/src/nest/registration/registration.service.ts b/packages/backend/src/nest/registration/registration.service.ts index d92a1c8738..2640e14cea 100644 --- a/packages/backend/src/nest/registration/registration.service.ts +++ b/packages/backend/src/nest/registration/registration.service.ts @@ -31,7 +31,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { private _server: Server private _port: number public registrationService: any - public certificates: string[] + public certificates: string[] = [] private _permsData: PermsData private _ownerCertificate: string @@ -43,6 +43,13 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { this.on(RegistrationEvents.SET_CERTIFICATES, certs => { this.setCertificates(certs) }) + this.on(RegistrationEvents.REGISTER_USER_CERTIFICATE, async (csr: string) => { + if (!this._permsData) { + console.log('NO PERMS DATA') + return + } + await this.registerUser(csr) + }) // eslint-disable-next-line const self = this this.setRouting(self) @@ -96,6 +103,14 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { }) } + public set permsData(perms: PermsData) { + console.log('Setting owner perms data') + this._permsData = { + certificate: perms.certificate, + privKey: perms.privKey, + } + } + public async registerOwnerCertificate(payload: RegisterOwnerCertificatePayload): Promise { let cert: string try { @@ -135,7 +150,7 @@ export class RegistrationService extends EventEmitter implements OnModuleInit { this.emit(response.eventType, response.data) } - private async registerUser(csr: string): Promise<{ status: number; body: any }> { + public async registerUser(csr: string): Promise<{ status: number; body: any }> { const result = await registerUser(csr, this._permsData, this.certificates, this._ownerCertificate) if (result?.status === 200) { this.emit(RegistrationEvents.NEW_USER, { certificate: result.body.certificate, rootPermsData: this._permsData }) diff --git a/packages/backend/src/nest/registration/registration.types.ts b/packages/backend/src/nest/registration/registration.types.ts index 62e81b59c1..dcffe9d7f9 100644 --- a/packages/backend/src/nest/registration/registration.types.ts +++ b/packages/backend/src/nest/registration/registration.types.ts @@ -4,4 +4,5 @@ export enum RegistrationEvents { NEW_USER = 'newUser', SET_CERTIFICATES = 'setCertificates', REGISTRAR_STATE = 'registrarState', + REGISTER_USER_CERTIFICATE = 'registerUserCertificate', } diff --git a/packages/backend/src/nest/socket/socket.service.spec.ts b/packages/backend/src/nest/socket/socket.service.spec.ts index 6fb8613281..f32ba21103 100644 --- a/packages/backend/src/nest/socket/socket.service.spec.ts +++ b/packages/backend/src/nest/socket/socket.service.spec.ts @@ -1,27 +1,74 @@ +import { jest } from '@jest/globals' import { Test, TestingModule } from '@nestjs/testing' import { TestModule } from '../common/test.module' import { SocketModule } from './socket.module' import { SocketService } from './socket.service' +import { io, Socket } from 'socket.io-client' +import waitForExpect from 'wait-for-expect' +import { SocketActionTypes } from '@quiet/types' +import { suspendableSocketEvents } from './suspendable.events' +import { TEST_DATA_PORT } from '../const' describe('SocketService', () => { let module: TestingModule let socketService: SocketService + let client: Socket + beforeAll(async () => { module = await Test.createTestingModule({ imports: [TestModule, SocketModule], }).compile() socketService = await module.resolve(SocketService) + + module.init() + + client = io(`http://127.0.0.1:${TEST_DATA_PORT}`) }) afterAll(async () => { + client.close() + socketService.close() + await module.close() }) - it('start and stop data server', async () => { + it('sets no default cors', async () => { expect(socketService.serverIoProvider.io.engine.opts.cors).toStrictEqual({}) // No cors should be set by default - await socketService.listen() - await socketService.close() + }) + + it('suspends events handling until backend is fully initialized', async () => { + const spy = jest.spyOn(socketService, 'emit') + + const event = suspendableSocketEvents[0] + + client.emit(event) + + expect(spy).not.toBeCalledWith(event, undefined) + + socketService.resolveReadyness() + + await waitForExpect(() => { + expect(spy).toHaveBeenCalledWith(event, undefined) + }) + }) + + it('there are no fragile endpoints in the collection of suspendables', async () => { + const fragile: string[] = [ + SocketActionTypes.CREATE_NETWORK.valueOf(), + SocketActionTypes.CREATE_COMMUNITY.valueOf(), + SocketActionTypes.LAUNCH_COMMUNITY.valueOf(), + SocketActionTypes.LAUNCH_REGISTRAR.valueOf(), + SocketActionTypes.REGISTER_OWNER_CERTIFICATE.valueOf(), + SocketActionTypes.REGISTER_USER_CERTIFICATE.valueOf(), + SocketActionTypes.SAVE_OWNER_CERTIFICATE.valueOf(), + SocketActionTypes.SAVE_USER_CSR.valueOf(), + SocketActionTypes.SEND_COMMUNITY_METADATA.valueOf(), + ] + + fragile.forEach(event => { + expect(suspendableSocketEvents).not.toContain(event) + }) }) }) diff --git a/packages/backend/src/nest/socket/socket.service.ts b/packages/backend/src/nest/socket/socket.service.ts index 77d129c2e8..073636a08b 100644 --- a/packages/backend/src/nest/socket/socket.service.ts +++ b/packages/backend/src/nest/socket/socket.service.ts @@ -7,7 +7,6 @@ import { DownloadFilePayload, CancelDownloadPayload, AskForMessagesPayload, - RegisterUserCertificatePayload, ConnectionProcessInfo, RegisterOwnerCertificatePayload, SaveOwnerCertificatePayload, @@ -15,27 +14,39 @@ import { LaunchRegistrarPayload, Community, DeleteFilesFromChannelSocketPayload, + SaveCSRPayload, + CommunityMetadata, } from '@quiet/types' -import cors, { CorsOptions } from 'cors' import EventEmitter from 'events' import { CONFIG_OPTIONS, SERVER_IO_PROVIDER } from '../const' import { ConfigOptions, ServerIoProviderTypes } from '../types' +import { suspendableSocketEvents } from './suspendable.events' import Logger from '../common/logger' @Injectable() export class SocketService extends EventEmitter implements OnModuleInit { private readonly logger = Logger(SocketService.name) + + public resolveReadyness: (value: void | PromiseLike) => void + public readyness: Promise + constructor( @Inject(SERVER_IO_PROVIDER) public readonly serverIoProvider: ServerIoProviderTypes, @Inject(CONFIG_OPTIONS) public readonly configOptions: ConfigOptions ) { super() + + this.readyness = new Promise(resolve => { + this.resolveReadyness = resolve + }) } async onModuleInit() { this.logger('init:started') + this.attachListeners() await this.init() + this.logger('init:finished') } @@ -46,6 +57,7 @@ export class SocketService extends EventEmitter implements OnModuleInit { resolve() }) }) + await this.listen() await connection @@ -55,95 +67,149 @@ export class SocketService extends EventEmitter implements OnModuleInit { // Attach listeners here this.serverIoProvider.io.on(SocketActionTypes.CONNECTION, socket => { this.logger('socket connection') + // On websocket connection, update presentation service with network data this.emit(SocketActionTypes.CONNECTION) + socket.on(SocketActionTypes.CLOSE, async () => { this.emit(SocketActionTypes.CLOSE) }) + + socket.use(async (event, next) => { + const type = event[0] + if (suspendableSocketEvents.includes(type)) { + this.logger('Awaiting readyness before emitting: ', type) + await this.readyness + } + next() + }) + + // ====== Channels ===== socket.on(SocketActionTypes.CREATE_CHANNEL, async (payload: CreateChannelPayload) => { this.emit(SocketActionTypes.CREATE_CHANNEL, payload) }) + + socket.on(SocketActionTypes.DELETE_CHANNEL, async (payload: { channelId: string; ownerPeerId: string }) => { + this.emit(SocketActionTypes.DELETE_CHANNEL, payload) + }) + + // ====== Messages ====== socket.on(SocketActionTypes.SEND_MESSAGE, async (payload: SendMessagePayload) => { this.emit(SocketActionTypes.SEND_MESSAGE, payload) }) + + socket.on(SocketActionTypes.SUBSCRIBE_FOR_ALL_CONVERSATIONS, async (peerId: string, conversations: string[]) => { + this.emit(SocketActionTypes.SUBSCRIBE_FOR_ALL_CONVERSATIONS, { peerId, conversations }) + }) + + socket.on(SocketActionTypes.ASK_FOR_MESSAGES, async (payload: AskForMessagesPayload) => { + this.emit(SocketActionTypes.ASK_FOR_MESSAGES, payload) + }) + + // ====== Files ====== socket.on(SocketActionTypes.UPLOAD_FILE, async (payload: UploadFilePayload) => { this.emit(SocketActionTypes.UPLOAD_FILE, payload.file) }) + socket.on(SocketActionTypes.DOWNLOAD_FILE, async (payload: DownloadFilePayload) => { this.emit(SocketActionTypes.DOWNLOAD_FILE, payload.metadata) }) + socket.on(SocketActionTypes.CANCEL_DOWNLOAD, async (payload: CancelDownloadPayload) => { this.emit(SocketActionTypes.CANCEL_DOWNLOAD, payload.mid) }) + + socket.on(SocketActionTypes.DELETE_FILES_FROM_CHANNEL, async (payload: DeleteFilesFromChannelSocketPayload) => { + this.emit(SocketActionTypes.DELETE_FILES_FROM_CHANNEL, payload) + }) + + // ====== Direct Messages ====== socket.on( SocketActionTypes.INITIALIZE_CONVERSATION, async (peerId: string, { address, encryptedPhrase }: { address: string; encryptedPhrase: string }) => { this.emit(SocketActionTypes.INITIALIZE_CONVERSATION, { address, encryptedPhrase }) } ) + socket.on(SocketActionTypes.GET_PRIVATE_CONVERSATIONS, async (peerId: string) => { this.emit(SocketActionTypes.GET_PRIVATE_CONVERSATIONS, { peerId }) }) + socket.on( SocketActionTypes.SEND_DIRECT_MESSAGE, async (peerId: string, { channelId, message }: { channelId: string; message: string }) => { this.emit(SocketActionTypes.SEND_DIRECT_MESSAGE, { channelId, message }) } ) + socket.on(SocketActionTypes.SUBSCRIBE_FOR_DIRECT_MESSAGE_THREAD, async (peerId: string, channelId: string) => { this.emit(SocketActionTypes.SUBSCRIBE_FOR_DIRECT_MESSAGE_THREAD, { peerId, channelId }) }) - socket.on(SocketActionTypes.SUBSCRIBE_FOR_ALL_CONVERSATIONS, async (peerId: string, conversations: string[]) => { - this.emit(SocketActionTypes.SUBSCRIBE_FOR_ALL_CONVERSATIONS, { peerId, conversations }) - }) - socket.on(SocketActionTypes.ASK_FOR_MESSAGES, async (payload: AskForMessagesPayload) => { - this.emit(SocketActionTypes.ASK_FOR_MESSAGES, payload) - }) - socket.on(SocketActionTypes.REGISTER_USER_CERTIFICATE, async (payload: RegisterUserCertificatePayload) => { - this.logger(`Registering user certificate (${payload.communityId}) on ${payload.serviceAddress}`) - this.emit(SocketActionTypes.REGISTER_USER_CERTIFICATE, payload) + // ====== Certificates ====== + socket.on(SocketActionTypes.SAVE_USER_CSR, async (payload: SaveCSRPayload) => { + this.logger(`On ${SocketActionTypes.SAVE_USER_CSR}`) + + this.emit(SocketActionTypes.SAVE_USER_CSR, payload) + await new Promise(resolve => setTimeout(() => resolve(), 2000)) - this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.REGISTERING_USER_CERTIFICATE) + + this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.WAITING_FOR_METADATA) }) + socket.on(SocketActionTypes.REGISTER_OWNER_CERTIFICATE, async (payload: RegisterOwnerCertificatePayload) => { this.logger(`Registering owner certificate (${payload.communityId})`) + this.emit(SocketActionTypes.REGISTER_OWNER_CERTIFICATE, payload) this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.REGISTERING_OWNER_CERTIFICATE) }) + socket.on(SocketActionTypes.SAVE_OWNER_CERTIFICATE, async (payload: SaveOwnerCertificatePayload) => { this.logger(`Saving owner certificate (${payload.peerId}), community: ${payload.id}`) + this.emit(SocketActionTypes.SAVED_OWNER_CERTIFICATE, payload) + + const communityMetadataPayload: CommunityMetadata = { + id: payload.id, + ownerCertificate: payload.certificate, + rootCa: payload.permsData.certificate, + } + + console.log('Metadata from state-manager', communityMetadataPayload) + + this.emit(SocketActionTypes.SEND_COMMUNITY_METADATA, communityMetadataPayload) + }) + + // ====== Community ====== + socket.on(SocketActionTypes.SEND_COMMUNITY_METADATA, (payload: CommunityMetadata) => { + this.emit(SocketActionTypes.SEND_COMMUNITY_METADATA, payload) }) + socket.on(SocketActionTypes.CREATE_COMMUNITY, async (payload: InitCommunityPayload) => { this.logger(`Creating community ${payload.id}`) this.emit(SocketActionTypes.CREATE_COMMUNITY, payload) }) + socket.on(SocketActionTypes.LAUNCH_COMMUNITY, async (payload: InitCommunityPayload) => { this.logger(`Launching community ${payload.id} for ${payload.peerId.id}`) this.emit(SocketActionTypes.LAUNCH_COMMUNITY, payload) this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.LAUNCHING_COMMUNITY) }) + socket.on(SocketActionTypes.LAUNCH_REGISTRAR, async (payload: LaunchRegistrarPayload) => { this.logger(`Launching registrar for community ${payload.id}, user ${payload.peerId}`) this.emit(SocketActionTypes.LAUNCH_REGISTRAR, payload) }) + socket.on(SocketActionTypes.CREATE_NETWORK, async (community: Community) => { this.logger(`Creating network for community ${community.id}`) this.emit(SocketActionTypes.CREATE_NETWORK, community) }) + socket.on(SocketActionTypes.LEAVE_COMMUNITY, async () => { - this.logger('leaving community') + this.logger('Leaving community') this.emit(SocketActionTypes.LEAVE_COMMUNITY) }) - socket.on(SocketActionTypes.DELETE_CHANNEL, async (payload: { channelId: string; ownerPeerId: string }) => { - this.logger('deleting channel ', payload.channelId) - this.emit(SocketActionTypes.DELETE_CHANNEL, payload) - }) - socket.on(SocketActionTypes.DELETE_FILES_FROM_CHANNEL, async (payload: DeleteFilesFromChannelSocketPayload) => { - this.logger('DELETE_FILES_FROM_CHANNEL', payload) - this.emit(SocketActionTypes.DELETE_FILES_FROM_CHANNEL, payload) - }) }) } diff --git a/packages/backend/src/nest/socket/suspendable.events.ts b/packages/backend/src/nest/socket/suspendable.events.ts new file mode 100644 index 0000000000..f87bce2f45 --- /dev/null +++ b/packages/backend/src/nest/socket/suspendable.events.ts @@ -0,0 +1,26 @@ +import { SocketActionTypes } from '@quiet/types' + +export const suspendableSocketEvents: string[] = [ + // Channels + SocketActionTypes.CREATE_CHANNEL.valueOf(), + SocketActionTypes.DELETE_CHANNEL.valueOf(), + + // Files + SocketActionTypes.UPLOAD_FILE.valueOf(), + SocketActionTypes.DOWNLOAD_FILE.valueOf(), + SocketActionTypes.CANCEL_DOWNLOAD.valueOf(), + SocketActionTypes.CHECK_FOR_MISSING_FILES.valueOf(), + SocketActionTypes.DELETE_FILES_FROM_CHANNEL.valueOf(), + + // Messages + SocketActionTypes.SEND_MESSAGE.valueOf(), + SocketActionTypes.ASK_FOR_MESSAGES.valueOf(), + SocketActionTypes.UPDATE_MESSAGE_MEDIA.valueOf(), + + // Private Conversations + SocketActionTypes.SUBSCRIBE_FOR_ALL_CONVERSATIONS.valueOf(), + SocketActionTypes.GET_PRIVATE_CONVERSATIONS.valueOf(), + SocketActionTypes.INITIALIZE_CONVERSATION.valueOf(), + SocketActionTypes.SEND_DIRECT_MESSAGE.valueOf(), + SocketActionTypes.SUBSCRIBE_FOR_DIRECT_MESSAGE_THREAD.valueOf(), +] diff --git a/packages/backend/src/nest/storage/storage.service.spec.ts b/packages/backend/src/nest/storage/storage.service.spec.ts index 8e95303aad..9012cf0785 100644 --- a/packages/backend/src/nest/storage/storage.service.spec.ts +++ b/packages/backend/src/nest/storage/storage.service.spec.ts @@ -255,6 +255,10 @@ describe('StorageService', () => { const channelsDbAddress = storageService.channels?.address // @ts-expect-error 'certificates' is private const certificatesDbAddress = storageService.certificates.address + // @ts-expect-error 'certificatesRequests' is private + const certificatesRequestsDbAddress = storageService.certificatesRequests.address + // @ts-expect-error 'communityMetadata' is private + const communityMetadataDbAddress = storageService.communityMetadata.address expect(channelsDbAddress).not.toBeFalsy() expect(certificatesDbAddress).not.toBeFalsy() expect(subscribeToPubSubSpy).toBeCalledTimes(2) @@ -262,6 +266,8 @@ describe('StorageService', () => { expect(subscribeToPubSubSpy).toHaveBeenNthCalledWith(1, [ StorageService.dbAddress(channelsDbAddress), StorageService.dbAddress(certificatesDbAddress), + StorageService.dbAddress(certificatesRequestsDbAddress), + StorageService.dbAddress(communityMetadataDbAddress), ]) // Creating channel: expect(subscribeToPubSubSpy).toHaveBeenNthCalledWith(2, [StorageService.dbAddress(db.address)]) @@ -540,7 +546,7 @@ describe('StorageService', () => { }) describe('Users', () => { - it('gets all users from db', async () => { + it('gets all registered users from db', async () => { await storageService.init(peerId) const mockGetCertificates = jest.fn() // @ts-ignore - Property 'getAllEventLogEntries' is protected @@ -549,7 +555,7 @@ describe('StorageService', () => { 'MIICWzCCAgGgAwIBAgIGAYKIVrmoMAoGCCqGSM49BAMCMA8xDTALBgNVBAMTBG1haW4wHhcNMjIwODEwMTUxOTIxWhcNMzAwMTMxMjMwMDAwWjBJMUcwRQYDVQQDEz5wM29xZHI1M2RrZ2czbjVudWV6bHp5YXdoeHZpdDVlZnh6bHVudnpwN243bG12YTZmajNpNDNhZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCAjxbiV781WC8O5emEdavPaQfR0FD8CaqC+P3R3uRdL9xuzGeUu8f5NIplSJ6abBMnanGgcMs34u82buiFROHqjggENMIIBCTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIAgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwLwYJKoZIhvcNAQkMBCIEICSr5xj+pjBSb+YOZ7TMPQJHYs4KASfnc9TugSpKJUG/MBUGCisGAQQBg4wbAgEEBxMFZGV2dnYwPQYJKwYBAgEPAwEBBDATLlFtVlRrVWFkMkdxM01rQ2E4Z2YxMlIxZ3NXRGZrMnlpVEVxYjZZR1hERzJpUTMwSQYDVR0RBEIwQII+cDNvcWRyNTNka2dnM241bnVlemx6eWF3aHh2aXQ1ZWZ4emx1bnZ6cDduN2xtdmE2ZmozaTQzYWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIhAIXhkkgs3H6GcZ1GYrSL2qJYDRQcpZlmcbq7YjpJHaORAiBMfkwP75v08R/ud6BPWvdS36corT+596+HzpqFt6bffw==', 'MIICYTCCAgegAwIBAgIGAYKIYnYuMAoGCCqGSM49BAMCMA8xDTALBgNVBAMTBG1haW4wHhcNMjIwODEwMTUzMjEwWhcNMzAwMTMxMjMwMDAwWjBJMUcwRQYDVQQDEz52bnl3dWl5bDdwN2lnMm11cmNzY2R5emtza281M2U0azNkcGRtMnlvb3B2dnUyNXA2d3dqcWJhZC5vbmlvbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM0cOt7jMJ6YhRvL9nhbDCh42QJPKDet/Zc2PJ9rm6CzYz1IXc5uRUCUNZSnNykVMZknogAavp0FjV+cFXzV8gGjggETMIIBDzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIAgDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwLwYJKoZIhvcNAQkMBCIEIIsBwPwIhLSltj9dnkgkMq3sOe3RVha9Mhukop6XOoISMBsGCisGAQQBg4wbAgEEDRMLZHNrZmpia3NmaWcwPQYJKwYBAgEPAwEBBDATLlFtZDJVbjlBeW5va1pyY1pHc011YXFndXBUdGlkSEdRblVrTlZmRkZBZWY5N0MwSQYDVR0RBEIwQII+dm55d3VpeWw3cDdpZzJtdXJjc2NkeXprc2tvNTNlNGszZHBkbTJ5b29wdnZ1MjVwNnd3anFiYWQub25pb24wCgYIKoZIzj0EAwIDSAAwRQIgAiCmGfUuSG010CxLEzu9mAQOgDq//SHI9LkXbmCxaAUCIQC9xzmkRBxq5HmNomYJ9ZAJXaY3J6+VqBYthaVnv0bhMw==', ]) - const allUsers = storageService.getAllUsers() + const allUsers = storageService.getAllRegisteredUsers() expect(allUsers).toStrictEqual([ { onionAddress: 'p3oqdr53dkgg3n5nuezlzyawhxvit5efxzlunvzp7n7lmva6fj3i43ad.onion', @@ -565,6 +571,34 @@ describe('StorageService', () => { }, ]) }) + it('gets all users from db', async () => { + await storageService.init(peerId) + const mockGetCsrs = jest.fn() + // @ts-ignore - Property 'getAllEventLogEntries' is protected + storageService.getAllEventLogEntries = mockGetCsrs + mockGetCsrs.mockReturnValue([ + 'MIIDHjCCAsMCAQAwSTFHMEUGA1UEAxM+NnZ1MmJ4a2k3NzdpdDNjcGF5djZmcTZ2cGw0a2Uza3pqN2d4aWNmeWdtNTVkaGh0cGh5ZmR2eWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMpfp2hSfWFL26OZlZKZEWG9fyAM1ndlEzO0kLxT0pA/7/fs+a5X/s4TkzqCVVQSzhas/84q0WE99ScAcM1LQJoIICFjAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBR6VRzktP1pzZxsGUaJivNUrtgSrzCCAUcGCSqGSIb3DQEJDDGCATgEggE0KZq9s6HEViRfplVgYkulg6XV411ZRe4U1UjfXTf1pRaygfcenGbT6RRagPtZzjuq5hHdYhqDjRzZhnbn8ZASYTgBM7qcseUq5UpS1pE08DI2jePKqatp3Pzm6a/MGSziESnREx784JlKfwKMjJl33UA8lQm9nhSeAIHyBx3c4Lf8IXdW2n3rnhbVfjpBMAxwh6lt+e5agtGXy+q/xAESUeLPfUgRYWctlLgt8Op+WTpLyBkZsVFoBvJrMt2XdM0RI32YzTRr56GXFa4VyQmY5xXwlQSPgidAP7jPkVygNcoeXvAz2ZCk3IR1Cn3mX8nMko53MlDNaMYldUQA0ug28/S7BlSlaq2CDD4Ol3swTq7C4KGTxKrI36ruYUZx7NEaQDF5V7VvqPCZ0fZoTIJuSYTQ67gwEQYKKwYBBAGDjBsCATEDEwFvMD0GCSsGAQIBDwMBATEwEy5RbVhSWTRyaEF4OE11cThkTUdrcjlxa25KZEU2VUhaRGRHYURSVFFFYndGTjViMEcGA1UdETFAEz42dnUyYnhraTc3N2l0M2NwYXl2NmZxNnZwbDRrZTNremo3Z3hpY2Z5Z201NWRoaHRwaHlmZHZ5ZC5vbmlvbjAKBggqhkjOPQQDAgNJADBGAiEAt+f1u/bchg5AZHv6NTGNoXeejTRWUhX3ioGwW6TGg84CIQCHqKNzDh2JjS/hUHx5PApAmfNnQTSf19X6LnNHQweU1g==', + 'MIIDHTCCAsMCAQAwSTFHMEUGA1UEAxM+eTd5Y3ptdWdsMnRla2FtaTdzYmR6NXBmYWVtdng3YmFod3RocmR2Y2J6dzV2ZXgyY3JzcjI2cWQub25pb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMq0l4bCmjdb0grtzpwtDVLM9E1IQpL9vrB4+lD9OBZzlrx2365jV7shVu9utas8w8fxtKoBZSnT5+32ZMFTB4oIICFjAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBSoDQpTZdEvi1/Rr/muVXT1clyKRDCCAUcGCSqGSIb3DQEJDDGCATgEggE0BQvyvkiiXEf/PLKnsR1Ba9AhYsVO8o56bnftUnoVzBlRZgUzLJvOSroPk/EmbVz+okhMrcYNgCWHvxrAqHVVq0JRP6bi98BtCUotx6OPFHp5K5QCL60hod1uAnhKocyJG9tsoM9aS+krn/k+g4RCBjiPZ25cC7QG/UNr6wyIQ8elBho4MKm8iOp7EShSsZOV1f6xrnXYCC/zyUc85GEuycLzVImgAQvPATbdMzY4zSGnNLHxkvSUNxaR9LnEWf+i1jeqcOiXOvmdyU5Be3ZqhGKvvBg/5vyLQiCIfeapjZemnLqFHQBitglDm2xnKL6HzMyfZoAHPV7YcWYR4spU9Ju8Q8aqSeAryx7sx55eSR4GO5UQTo5DrQn6xtkwOZ/ytsOknFthF8jcA9uTAMDKA2TylCUwEQYKKwYBBAGDjBsCATEDEwFvMD0GCSsGAQIBDwMBATEwEy5RbVQxOFV2blVCa3NlTWMzU3FuZlB4cEh3TjhuekxySmVOU0xadGM4ckFGWGh6MEcGA1UdETFAEz55N3ljem11Z2wydGVrYW1pN3NiZHo1cGZhZW12eDdiYWh3dGhyZHZjYnp3NXZleDJjcnNyMjZxZC5vbmlvbjAKBggqhkjOPQQDAgNIADBFAiEAoFrAglxmk7ciD6AHQOB1qEoLu0NARcxgwmIry8oeTHwCICyXp5NJQ9Z8vReIAQNng2H2+/XjHifZEWzhoN0VkcBx', + ]) + const allUsers = storageService.getAllUsers() + + expect(allUsers).toStrictEqual([ + { + onionAddress: '6vu2bxki777it3cpayv6fq6vpl4ke3kzj7gxicfygm55dhhtphyfdvyd.onion', + peerId: 'QmXRY4rhAx8Muq8dMGkr9qknJdE6UHZDdGaDRTQEbwFN5b', + dmPublicKey: + '299abdb3a1c456245fa65560624ba583a5d5e35d5945ee14d548df5d37f5a516b281f71e9c66d3e9145a80fb59ce3baae611dd621a838d1cd98676e7f1901261380133ba9cb1e52ae54a52d69134f032368de3caa9ab69dcfce6e9afcc192ce21129d1131efce0994a7f028c8c9977dd403c9509bd9e149e0081f2071ddce0b7fc217756da7deb9e16d57e3a41300c7087a96df9ee5a82d197cbeabfc4011251e2cf7d481161672d94b82df0ea7e593a4bc81919b1516806f26b32dd9774cd11237d98cd346be7a19715ae15c90998e715f095048f8227403fb8cf915ca035ca1e5ef033d990a4dc84750a7de65fc9cc928e773250cd68c625754400d2e836f3f4bb0654a56aad820c3e0e977b304eaec2e0a193c4aac8dfaaee614671ecd11a40317957b56fa8f099d1f6684c826e4984d0ebb8', + username: 'o', + }, + { + onionAddress: 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd.onion', + peerId: 'QmT18UvnUBkseMc3SqnfPxpHwN8nzLrJeNSLZtc8rAFXhz', + dmPublicKey: + '050bf2be48a25c47ff3cb2a7b11d416bd02162c54ef28e7a6e77ed527a15cc19516605332c9bce4aba0f93f1266d5cfea2484cadc60d802587bf1ac0a87555ab42513fa6e2f7c06d094a2dc7a38f147a792b94022fad21a1dd6e02784aa1cc891bdb6ca0cf5a4be92b9ff93e83844206388f676e5c0bb406fd436beb0c8843c7a5061a3830a9bc88ea7b112852b19395d5feb1ae75d8082ff3c9473ce4612ec9c2f35489a0010bcf0136dd333638cd21a734b1f192f494371691f4b9c459ffa2d637aa70e8973af99dc94e417b766a8462afbc183fe6fc8b4220887de6a98d97a69cba851d0062b609439b6c6728be87cccc9f6680073d5ed8716611e2ca54f49bbc43c6aa49e02bcb1eecc79e5e491e063b95104e8e43ad09fac6d930399ff2b6c3a49c5b6117c8dc03db9300c0ca0364f29425', + username: 'o', + }, + ]) + }) }) describe('Files deletion', () => { diff --git a/packages/backend/src/nest/storage/storage.service.ts b/packages/backend/src/nest/storage/storage.service.ts index 1b2300e9d3..dbe0a8622b 100644 --- a/packages/backend/src/nest/storage/storage.service.ts +++ b/packages/backend/src/nest/storage/storage.service.ts @@ -7,6 +7,8 @@ import { parseCertificate, verifySignature, verifyUserCert, + parseCertificationRequest, + getReqFieldValue, } from '@quiet/identity' import type { IPFS } from 'ipfs-core' import OrbitDB from 'orbit-db' @@ -15,21 +17,23 @@ import KeyValueStore from 'orbit-db-kvstore' import path from 'path' import { EventEmitter } from 'events' import PeerId from 'peer-id' -import { getCrypto } from 'pkijs' +import { CertificationRequest, getCrypto } from 'pkijs' import { stringToArrayBuffer } from 'pvutils' import validate from '../validation/validators' import { CID } from 'multiformats/cid' import { ChannelMessage, + CommunityMetadata, ConnectionProcessInfo, DeleteFilesFromChannelSocketPayload, FileMetadata, NoCryptoEngineError, PublicChannel, PushNotificationPayload, + SaveCSRPayload, SaveCertificatePayload, SocketActionTypes, - User, + UserData, } from '@quiet/types' import { isDefined } from '@quiet/common' import fs from 'fs' @@ -43,7 +47,7 @@ import AccessControllers from 'orbit-db-access-controllers' import { MessagesAccessController } from './MessagesAccessController' import { createChannelAccessController } from './ChannelsAccessController' import Logger from '../common/logger' -import { DirectMessagesRepo, IMessageThread, PublicChannelsRepo } from '../common/types' +import { DirectMessagesRepo, PublicChannelsRepo } from '../common/types' import { removeFiles, removeDirs, createPaths, getUsersAddresses } from '../common/utils' import { StorageEvents } from './storage.types' @@ -55,10 +59,12 @@ interface DBOptions { export class StorageService extends EventEmitter { public channels: KeyValueStore private certificates: EventStore + private certificatesRequests: EventStore public publicChannelsRepos: Map = new Map() public directMessagesRepos: Map = new Map() private publicKeysMap: Map = new Map() private userNamesMap: Map = new Map() + private communityMetadata: KeyValueStore private ipfs: IPFS private orbitDb: OrbitDB private filesManager: IpfsFileManagerService @@ -150,6 +156,12 @@ export class StorageService extends EventEmitter { if (this.certificates?.address) { dbs.push(this.certificates.address) } + if (this.certificatesRequests?.address) { + dbs.push(this.certificatesRequests.address) + } + if (this.communityMetadata?.address) { + dbs.push(this.communityMetadata.address) + } const channels = this.publicChannelsRepos.values() @@ -172,7 +184,7 @@ export class StorageService extends EventEmitter { return } for (const a of addr) { - this.logger(`Pubsub - subscribe to ${addr}`) + this.logger(`Pubsub - subscribe to ${a}`) // @ts-ignore await this.orbitDb._pubsub.subscribe( a, @@ -203,16 +215,53 @@ export class StorageService extends EventEmitter { } public async initDatabases() { - this.logger('1/4') + this.logger('1/5') await this.createDbForChannels() - this.logger('2/4') + this.logger('2/5') await this.createDbForCertificates() - this.logger('3/4') + this.logger('3/5') + await this.createDbForCertificatesRequests() + this.logger('4/5') + await this.createDbForCommunityMetadata() + this.logger('5/5') await this.initAllChannels() - this.logger('4/4') this.logger('Initialized DBs') this.emit(SocketActionTypes.CONNECTION_PROCESS_INFO, ConnectionProcessInfo.INITIALIZED_DBS) } + private async createDbForCommunityMetadata() { + this.logger('createDbForCommunityMetadata init') + this.communityMetadata = await this.orbitDb.keyvalue('community-metadata', { + replicate: false, + accessController: { + write: ['*'], + }, + }) + + this.communityMetadata.events.on('write', async (_address, _entry) => { + this.logger('WRITE: communityMetadata') + }) + + this.communityMetadata.events.on('replicated', async () => { + this.logger('Replicated community metadata') + // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + await this.communityMetadata.load({ fetchEntryTimeout: 15000 }) + this.emit(StorageEvents.REPLICATED_COMMUNITY_METADATA, Object.values(this.communityMetadata.all)[0]) + }) + // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + await this.communityMetadata.load({ fetchEntryTimeout: 15000 }) + } + + public async updateCommunityMetadata(communityMetadata: CommunityMetadata) { + this.logger(`About to update community metadata`) + if (!communityMetadata.id) return + const meta = this.communityMetadata.get(communityMetadata.id) + if (meta?.ownerCertificate && meta?.rootCa) return + this.logger(`Updating community metadata`) + await this.communityMetadata.put(communityMetadata.id, { + ...meta, + ...communityMetadata, + }) + } private async __stopOrbitDb() { if (this.orbitDb) { @@ -245,19 +294,25 @@ export class StorageService extends EventEmitter { public async stopOrbitDb() { try { - if (this.channels) { - await this.channels.close() - } + await this.channels?.close() } catch (e) { - this.logger.error('channels', e) + this.logger.error('Error closing channels db', e) } try { - if (this.certificates) { - await this.certificates.close() - } + await this.certificates?.close() } catch (e) { - this.logger.error('certificates', e) + this.logger.error('Error closing certificates db', e) + } + try { + await this.certificatesRequests?.close() + } catch (e) { + this.logger.error('Error closing certificates db', e) + } + try { + await this.communityMetadata?.close() + } catch (e) { + this.logger.error('Error closing community metadata db', e) } await this.__stopOrbitDb() await this.__stopIPFS() @@ -265,7 +320,9 @@ export class StorageService extends EventEmitter { public async updatePeersList() { const allUsers = this.getAllUsers() - const peers = await getUsersAddresses(allUsers) + const registeredUsers = this.getAllRegisteredUsers() + const peers = [...new Set(await getUsersAddresses(allUsers.concat(registeredUsers)))] + console.log('updatePeersList, peers count:', peers.length) const community = await this.localDbService.get(LocalDBKeys.COMMUNITY) this.emit(StorageEvents.UPDATE_PEERS_LIST, { communityId: community.id, peerList: peers }) } @@ -332,6 +389,54 @@ export class StorageService extends EventEmitter { this.logger('STORAGE: Finished createDbForCertificates') } + public async createDbForCertificatesRequests() { + this.logger('certificatesRequests db init') + this.certificatesRequests = await this.orbitDb.log('csrs', { + replicate: false, + accessController: { + write: ['*'], + }, + }) + this.certificatesRequests.events.on('replicate.progress', async (_address, _hash, entry, _progress, _total) => { + const csr: string = entry.payload.value + this.logger('Replicated csr') + let parsedCSR: CertificationRequest + try { + parsedCSR = parseCertificationRequest(csr) + } catch (e) { + this.logger.error(`csrs replicate.progress: could not parse certificate request`) + return + } + + const username = getReqFieldValue(parsedCSR, CertFieldsTypes.nickName) + if (!username) { + this.logger.error( + `csrs replicate.progress: could not parse certificate request for field type ${CertFieldsTypes.nickName}` + ) + return + } + this.emit(StorageEvents.REPLICATED_CSR, [csr]) + }) + this.certificatesRequests.events.on('replicated', async () => { + this.logger('REPLICATED: CSRs') + const allCsrs = this.getAllEventLogEntries(this.certificatesRequests) + this.emit(StorageEvents.REPLICATED_CSR, allCsrs) + await this.updatePeersList() + }) + this.certificatesRequests.events.on('write', async (_address, entry) => { + const csr: string = entry.payload.value + this.logger('Saved CSR locally') + this.emit(StorageEvents.REPLICATED_CSR, [csr]) + await this.updatePeersList() + }) + + // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' + await this.certificatesRequests.load({ fetchEntryTimeout: 15000 }) + const allcsrs = this.getAllEventLogEntries(this.certificatesRequests) + this.logger('ALL Certificates COUNT:', allcsrs.length) + this.logger('STORAGE: Finished creating certificatesRequests db') + } + public async loadAllChannels() { this.logger('Getting all channels') // @ts-expect-error - OrbitDB's type declaration of `load` lacks 'options' @@ -755,9 +860,34 @@ export class StorageService extends EventEmitter { return true } - public getAllUsers(): User[] { + public async saveCSR(payload: SaveCSRPayload): Promise { + this.logger('About to save csr...') + if (!payload.csr) { + this.logger('CSR is either null or undefined, not saving to db') + return false + } + // TODO: Verify CSR + try { + parseCertificationRequest(payload.csr) + } catch (e) { + this.logger.error(`Cannot save csr ${payload.csr}. Reason: ${e.message}`) + return false + } + + await this.certificatesRequests.load() + + const csrs = this.getAllEventLogEntries(this.certificatesRequests) + + if (csrs.includes(payload.csr)) return false + + this.logger('Saving csr...') + await this.certificatesRequests.add(payload.csr) + return true + } + + public getAllRegisteredUsers(): UserData[] { const certs = this.getAllEventLogEntries(this.certificates) - const allUsers: User[] = [] + const allUsers: UserData[] = [] for (const cert of certs) { const parsedCert = parseCertificate(cert) const onionAddress = getCertFieldValue(parsedCert, CertFieldsTypes.commonName) @@ -770,6 +900,22 @@ export class StorageService extends EventEmitter { return allUsers } + public getAllUsers(): UserData[] { + const csrs = this.getAllEventLogEntries(this.certificatesRequests) + console.log('csrs count:', csrs.length) + const allUsers: UserData[] = [] + for (const csr of csrs) { + const parsedCert = parseCertificationRequest(csr) + const onionAddress = getReqFieldValue(parsedCert, CertFieldsTypes.commonName) + const peerId = getReqFieldValue(parsedCert, CertFieldsTypes.peerId) + const username = getReqFieldValue(parsedCert, CertFieldsTypes.nickName) + const dmPublicKey = getReqFieldValue(parsedCert, CertFieldsTypes.dmPublicKey) + if (!onionAddress || !peerId || !username || !dmPublicKey) continue + allUsers.push({ onionAddress, peerId, username, dmPublicKey }) + } + return allUsers + } + public usernameCert(username: string): string | null { /** * Check if given username is already in use diff --git a/packages/backend/src/nest/storage/storage.types.ts b/packages/backend/src/nest/storage/storage.types.ts index 617b944333..84b3286b5d 100644 --- a/packages/backend/src/nest/storage/storage.types.ts +++ b/packages/backend/src/nest/storage/storage.types.ts @@ -4,6 +4,7 @@ export enum StorageEvents { // Peers UPDATE_PEERS_LIST = 'updatePeersList', LOAD_CERTIFICATES = 'loadCertificates', + REPLICATED_CSR = 'replicatedCsr', // Public Channels LOAD_PUBLIC_CHANNELS = 'loadPublicChannels', LOAD_ALL_PRIVATE_CONVERSATIONS = 'loadAllPrivateConversations', @@ -22,6 +23,8 @@ export enum StorageEvents { LOAD_ALL_DIRECT_MESSAGES = 'loadAllDirectMessages', // Misc SEND_PUSH_NOTIFICATION = 'sendPushNotification', + // Community + REPLICATED_COMMUNITY_METADATA = 'replicatedCommunityMetadata', } export interface InitStorageParams { communityId: string diff --git a/packages/backend/src/nest/tor/tor-control.service.ts b/packages/backend/src/nest/tor/tor-control.service.ts index 1c2e6f509b..5fa1426bea 100644 --- a/packages/backend/src/nest/tor/tor-control.service.ts +++ b/packages/backend/src/nest/tor/tor-control.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import net from 'net' import { CONFIG_OPTIONS, TOR_CONTROL_PARAMS } from '../const' import { ConfigOptions } from '../types' @@ -6,7 +6,7 @@ import { TorControlAuthType, TorControlParams } from './tor.types' import Logger from '../common/logger' @Injectable() -export class TorControl implements OnModuleInit { +export class TorControl { connection: net.Socket | null authString: string private readonly logger = Logger(TorControl.name) @@ -15,7 +15,7 @@ export class TorControl implements OnModuleInit { @Inject(CONFIG_OPTIONS) public configOptions: ConfigOptions ) {} - onModuleInit() { + private updateAuthString() { if (this.torControlParams.auth.type === TorControlAuthType.PASSWORD) { this.authString = 'AUTHENTICATE "' + this.torControlParams.auth.value + '"\r\n' } @@ -46,11 +46,12 @@ export class TorControl implements OnModuleInit { reject(new Error(`TOR: Control port error: ${data.toString() as string}`)) } }) + this.updateAuthString() this.connection.write(this.authString) }) } - private async disconnect() { + private disconnect() { try { this.connection?.end() } catch (e) { @@ -62,11 +63,12 @@ export class TorControl implements OnModuleInit { // eslint-disable-next-line @typescript-eslint/ban-types private async _sendCommand(command: string, resolve: Function, reject: Function) { await this.connect() + const connectionTimeout = setTimeout(() => { reject('TOR: Send command timeout') }, 5000) this.connection?.on('data', async data => { - await this.disconnect() + this.disconnect() const dataArray = data.toString().split(/\r?\n/) if (dataArray[0].startsWith('250')) { resolve({ code: 250, messages: dataArray }) diff --git a/packages/backend/src/nest/tor/tor.service.ts b/packages/backend/src/nest/tor/tor.service.ts index 250b2ca2ee..8fbbe162de 100644 --- a/packages/backend/src/nest/tor/tor.service.ts +++ b/packages/backend/src/nest/tor/tor.service.ts @@ -260,7 +260,7 @@ export class Tor extends EventEmitter implements OnModuleInit { public async spawnHiddenService({ targetPort, privKey, - virtPort = 443, + virtPort = 80, }: { targetPort: number privKey: string @@ -295,7 +295,7 @@ export class Tor extends EventEmitter implements OnModuleInit { public async createNewHiddenService({ targetPort, - virtPort = 443, + virtPort = 80, }: { targetPort: number virtPort?: number diff --git a/packages/backend/src/nest/websocketOverTor/index.ts b/packages/backend/src/nest/websocketOverTor/index.ts index 044700842e..929ac78cb2 100644 --- a/packages/backend/src/nest/websocketOverTor/index.ts +++ b/packages/backend/src/nest/websocketOverTor/index.ts @@ -1,22 +1,15 @@ import { socketToMaConn } from './socket-to-conn' import * as filters from './filters' - import { type MultiaddrFilter, type CreateListenerOptions, type DialOptions } from '@libp2p/interface-transport' import type { AbortOptions } from '@libp2p/interfaces' import type { Multiaddr } from '@multiformats/multiaddr' - import type { ClientOptions, ErrorEvent } from 'ws' - import os from 'os' import PeerId from 'peer-id' - import url from 'url' - import type { Server } from 'http' -import https from 'https' - +import * as http from 'http' import { EventEmitter } from 'events' - import pDefer from 'p-defer' import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri' import { AbortError } from '@libp2p/interfaces/errors' @@ -24,7 +17,6 @@ import { connect } from 'it-ws' import { type ServerOptions, type WebSocketServer as ItWsWebsocketServer } from 'it-ws/server' import { multiaddr } from '@multiformats/multiaddr' import { type MultiaddrConnection, type Connection } from '@libp2p/interface-connection' -import { dumpPEM } from './utils' import logger from '../common/logger' const log = logger('libp2p:websockets') @@ -88,7 +80,6 @@ export class WebSockets extends EventEmitter { socket = await this._connect(ma, { websocket: { ...this._websocketOpts, - ...this.certData, }, }) } catch (e) { @@ -113,24 +104,6 @@ export class WebSockets extends EventEmitter { } } - get certData() { - const { cert, key, ca } = this._websocketOpts - if (!cert || !key || !ca?.length || !ca[0]) { - throw new Error('No cert data in _websocketOpts') - } - let _ca: string | Buffer - if (Array.isArray(ca)) { - _ca = ca[0] - } else { - _ca = ca - } - return { - cert: dumpPEM('CERTIFICATE', cert.toString()), - key: dumpPEM('PRIVATE KEY', key.toString()), - ca: [dumpPEM('CERTIFICATE', _ca.toString())], - } - } - async _connect(ma: Multiaddr, options: any = {}) { if (options.signal?.aborted) { throw new AbortError() @@ -145,6 +118,7 @@ export class WebSockets extends EventEmitter { } const myUri = `${toUri(ma)}/?remoteAddress=${encodeURIComponent(this.localAddress)}` + const rawSocket = connect(myUri, Object.assign({ binary: true }, options)) if (rawSocket.socket.on) { @@ -203,14 +177,10 @@ export class WebSockets extends EventEmitter { server.__connections?.push(maConn) } - const serverHttps = https.createServer({ - ...this.certData, - requestCert: true, - enableTrace: false, - }) + const serverHttp = http.createServer() const optionsServ = { - server: serverHttps, + server: serverHttp, verifyClient: function (_info: any, done: (res: boolean) => void) { done(true) }, @@ -298,7 +268,7 @@ export class WebSockets extends EventEmitter { // we need to capture from the passed multiaddr if (listeningMultiaddr.toString().includes('ip4')) { let m = listeningMultiaddr.decapsulate('tcp') - m = m.encapsulate('/tcp/443/wss') + m = m.encapsulate('/tcp/80/ws') if (ipfsId) { m = m.encapsulate('/p2p/' + ipfsId) } diff --git a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts index c68573947c..1ffde5daa4 100644 --- a/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts +++ b/packages/backend/src/nest/websocketOverTor/websocketOverTor.tor.spec.ts @@ -4,13 +4,7 @@ import { multiaddr } from '@multiformats/multiaddr' import getPort from 'get-port' import { type DirResult } from 'tmp' import { jest, describe, it, expect, afterEach, beforeAll, afterAll } from '@jest/globals' -import { - createLibp2pAddress, - torBinForPlatform, - torDirForPlatform, - createTmpDir, - tmpQuietDirPath, -} from '../common/utils' +import { torBinForPlatform, torDirForPlatform, createTmpDir, tmpQuietDirPath } from '../common/utils' import { type CreateListenerOptions } from '@libp2p/interface-transport' import { createServer } from 'it-ws/server' import { createCertificatesTestHelper } from '../common/client-server' @@ -22,6 +16,7 @@ import { Tor } from '../tor/tor.service' import crypto from 'crypto' import { TorControl } from '../tor/tor-control.service' import { TorControlAuthType } from '../tor/tor.types' +import { createLibp2pAddress } from '@quiet/common' jest.setTimeout(120000) describe('websocketOverTor', () => { @@ -216,7 +211,7 @@ describe('websocketOverTor', () => { expect((onConnection.mock.calls[0][0] as any).remoteAddr).toEqual(remoteAddress) }) - it('rejects connection if user cert is invalid', async () => { + it.skip('rejects connection if user cert is invalid', async () => { const pems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) const anotherPems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) @@ -283,73 +278,4 @@ describe('websocketOverTor', () => { }) ).rejects.toBeTruthy() }) - - it('rejects connection if server cert is invalid', async () => { - const pems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) - const anotherPems = await createCertificatesTestHelper(`${service1.onionAddress}`, `${service2.onionAddress}`) - - const prepareListenerArg: CreateListenerOptions = { - handler: x => x, - upgrader: { - // @ts-expect-error - upgradeOutbound, - // @ts-expect-error - upgradeInbound, - }, - } - - const signal: AbortSignal = { - ...abortSignalOpts, - addEventListener, - removeEventListener, - } - - const peerId1 = 'Qme5NiSQ6V3cc3nyfYVtkkXDPGBSYEVUNCN5sM4DbyYc7s' - const peerId2 = 'QmeCWxba5Yk1ZAKogQJsaHXoAermE7PgFZqpqyKNg65cSN' - - const websocketsOverTorData1 = { - filter: all, - websocket: { - agent, - cert: anotherPems.servCert, - key: anotherPems.servKey, - ca: [pems.ca], - }, - localAddress: createLibp2pAddress(service1.onionAddress, peerId1), - targetPort: port1Target, - createServer, - } - - const websocketsOverTorData2 = { - filter: all, - websocket: { - agent, - cert: pems.servCert, - key: pems.servKey, - ca: [pems.ca], - }, - localAddress: createLibp2pAddress(service2.onionAddress, peerId2), - targetPort: port2Target, - createServer, - } - - const multiAddress = multiaddr(createLibp2pAddress(service1.onionAddress, peerId1)) - - const ws1 = webSockets(websocketsOverTorData1)() - const ws2 = webSockets(websocketsOverTorData2)() - - listener = await ws1.prepareListener(prepareListenerArg) - - await listener.listen(multiAddress) - - const onConnection = jest.fn() - listener.on('connection', onConnection) - - await expect( - ws2.dial(multiAddress, { - signal, - upgrader: prepareListenerArg.upgrader, - }) - ).rejects.toBeTruthy() - }) }) diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 85afe0be21..927083dec1 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -3,6 +3,57 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.0.0-alpha.18](https://github.com/ZbayApp/monorepo/compare/@quiet/common@2.0.0-alpha.3...@quiet/common@2.0.0-alpha.18) (2023-10-04) + +**Note:** Version bump only for package @quiet/common + + + + + +# [2.0.0-alpha.3](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.0-alpha.2...@quiet/common@2.0.0-alpha.3) (2023-09-19) + +**Note:** Version bump only for package @quiet/common + + + + + +# [2.0.0-alpha.2](https://github.com/TryQuiet/quiet/compare/@quiet/common@1.8.1...@quiet/common@2.0.0-alpha.2) (2023-09-18) + + +### Bug Fixes + +* common: capitalize should return null if no string provided ([47269fd](https://github.com/TryQuiet/quiet/commit/47269fd48150c93cb6ede2bf833be05d5f893ab8)) +* typo ([62adb7e](https://github.com/TryQuiet/quiet/commit/62adb7e3a11cee2da9418f11a79d38c871fb684e)), closes [/github.com/TryQuiet/quiet/pull/1727#discussion_r1302471153](https://github.com//github.com/TryQuiet/quiet/pull/1727/issues/discussion_r1302471153) +* typo in function name ([7e00db1](https://github.com/TryQuiet/quiet/commit/7e00db1eb8d868cfe6682a1fa75f7e90b3a496bb)) + + + + + +# [2.0.0-alpha.1](https://github.com/TryQuiet/quiet/compare/@quiet/common@2.0.0-alpha.0...@quiet/common@2.0.0-alpha.1) (2023-09-14) + + +### Bug Fixes + +* common: capitalize should return null if no string provided ([47269fd](https://github.com/TryQuiet/quiet/commit/47269fd48150c93cb6ede2bf833be05d5f893ab8)) +* typo ([62adb7e](https://github.com/TryQuiet/quiet/commit/62adb7e3a11cee2da9418f11a79d38c871fb684e)), closes [/github.com/TryQuiet/quiet/pull/1727#discussion_r1302471153](https://github.com//github.com/TryQuiet/quiet/pull/1727/issues/discussion_r1302471153) +* typo in function name ([7e00db1](https://github.com/TryQuiet/quiet/commit/7e00db1eb8d868cfe6682a1fa75f7e90b3a496bb)) + + + + + +# [2.0.0-alpha.0](https://github.com/TryQuiet/quiet/compare/@quiet/common@1.9.0-alpha.0...@quiet/common@2.0.0-alpha.0) (2023-09-01) + +**Note:** Version bump only for package @quiet/common + + + + + +# [1.9.0-alpha.0](/compare/@quiet/common@1.8.0...@quiet/common@1.9.0-alpha.0) (2023-08-29) ## [1.8.1](https://github.com/TryQuiet/quiet/compare/@quiet/common@1.8.0...@quiet/common@1.8.1) (2023-09-15) **Note:** Version bump only for package @quiet/common diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json index 3e16e4e4b4..998dc70725 100644 --- a/packages/common/package-lock.json +++ b/packages/common/package-lock.json @@ -1,12 +1,12 @@ { "name": "@quiet/common", - "version": "1.8.1", + "version": "2.0.0-alpha.18", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@quiet/common", - "version": "1.8.1", + "version": "2.0.0-alpha.18", "license": "ISC", "dependencies": { "cross-env": "^5.2.0", diff --git a/packages/common/package.json b/packages/common/package.json index 42e97a6157..309fa44c15 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,12 +1,13 @@ { "name": "@quiet/common", - "version": "1.8.1", + "version": "2.0.0-alpha.18", "description": "Common monorepo utils", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ "lib/**/*" ], + "private": true, "scripts": { "build": "tsc -p tsconfig.build.json", "prepare": "npm run build", @@ -17,7 +18,7 @@ "rmDist": "rimraf lib/" }, "devDependencies": { - "@quiet/eslint-config": "^1.3.0", + "@quiet/eslint-config": "^2.0.0-alpha.18", "@types/jest": "^26.0.23", "@types/node": "^17.0.21", "jest": "^26.6.3", @@ -25,7 +26,7 @@ "typescript": "^4.9.3" }, "dependencies": { - "@quiet/types": "^1.8.1", + "@quiet/types": "^2.0.0-alpha.18", "cross-env": "^5.2.0", "debug": "^4.3.1" }, diff --git a/packages/common/src/capitalize.test.ts b/packages/common/src/capitalize.test.ts index 96f7f19c03..dff9932699 100644 --- a/packages/common/src/capitalize.test.ts +++ b/packages/common/src/capitalize.test.ts @@ -6,10 +6,10 @@ describe('Capitalize first letter', () => { }) it("doesn't break if provided empty string", () => { - expect(capitalizeFirstLetter('')).toEqual(null) + expect(capitalizeFirstLetter('')).toEqual('') }) it("doesn't break if string is undefined", () => { - expect(capitalizeFirstLetter(undefined)).toEqual(null) + expect(capitalizeFirstLetter(undefined)).toEqual('') }) }) diff --git a/packages/common/src/capitalize.ts b/packages/common/src/capitalize.ts index 304d55b3fd..45ddb73aa7 100644 --- a/packages/common/src/capitalize.ts +++ b/packages/common/src/capitalize.ts @@ -1,5 +1,5 @@ export const capitalizeFirstLetter = (text: string): string => { - if (text?.length > 0) { + if (text) { return text.charAt(0).toUpperCase() + text.slice(1) } else { return '' diff --git a/packages/common/src/channelAddress.test.ts b/packages/common/src/channelAddress.test.ts new file mode 100644 index 0000000000..be47efdeb6 --- /dev/null +++ b/packages/common/src/channelAddress.test.ts @@ -0,0 +1,29 @@ +import { generateChannelId, getChannelNameFromChannelId } from './channelAddress' + +describe('Generate Channel Id', () => { + it('name "rockets" is the channel name', () => { + expect(generateChannelId('rockets')).toContain('rockets') + }) + + it('Should include hexadecimals characters in a determined structure (name + _ + 16 hex)', () => { + const channelName = 'rockets' + const randomBytesLength = 32 // 16 chars in hex + const underscoreLength = 1 + const expectedLength = channelName.length + underscoreLength + randomBytesLength + expect(generateChannelId('rockets')).toHaveLength(expectedLength) + }) +}) + +describe('Get Channel Name From Channel Id', () => { + it('Should return the channel name', () => { + const channelId = 'rockets_1faff74afc8daff3256275ce89d30528' + const channelName = 'rockets' + expect(getChannelNameFromChannelId(channelId)).toEqual(channelName) + }) + it('Should return the channel id if does not match the structure', () => { + const channelName = 'rockets' + const invalidChannelId = 'rockets+1faff74afc8daff3256275ce89d30528' + expect(getChannelNameFromChannelId(channelName)).toEqual(channelName) + expect(getChannelNameFromChannelId(invalidChannelId)).toEqual(invalidChannelId) + }) +}) diff --git a/packages/common/src/channelAddress.ts b/packages/common/src/channelAddress.ts index 1a61aad440..cc67c5a893 100644 --- a/packages/common/src/channelAddress.ts +++ b/packages/common/src/channelAddress.ts @@ -2,7 +2,7 @@ import crypto from 'crypto' export const generateChannelId = (channelName: string) => `${channelName}_${crypto.randomBytes(16).toString('hex')}` -export const getChannelNameFormChannelId = (channelId: string) => { +export const getChannelNameFromChannelId = (channelId: string) => { const index = channelId.indexOf('_') if (index === -1) { return channelId diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 914f45c769..67ce8a7b5b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -7,3 +7,4 @@ export * from './sortPeers' export * from './channelAddress' export * from './naming' export * from './fileData' +export * from './libp2p' diff --git a/packages/common/src/invitationCode.test.ts b/packages/common/src/invitationCode.test.ts index 78bd1420c1..28254563bc 100644 --- a/packages/common/src/invitationCode.test.ts +++ b/packages/common/src/invitationCode.test.ts @@ -1,24 +1,75 @@ -import { argvInvitationCode, invitationDeepUrl, invitationShareUrl } from './invitationCode' -import { Site } from './static' +import { + argvInvitationCode, + invitationDeepUrl, + invitationShareUrl, + pairsToInvitationShareUrl, + retrieveInvitationCode, +} from './invitationCode' +import { QUIET_JOIN_PAGE } from './static' describe('Invitation code helper', () => { + const peerId1 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA' + const address1 = 'gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad' + const peerId2 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE' + const address2 = 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd' + it('retrieves invitation code from argv', () => { + const expectedCodes = [ + { peerId: peerId1, onionAddress: address1 }, + { peerId: peerId2, onionAddress: address2 }, + ] const result = argvInvitationCode([ 'something', 'quiet:/invalid', 'zbay://invalid', 'quiet://invalid', 'quiet://?param=invalid', - invitationDeepUrl('validCode'), + invitationDeepUrl(expectedCodes), ]) - expect(result).toBe('validCode') + expect(result).toEqual(expectedCodes) }) it('builds proper invitation deep url', () => { - expect(invitationDeepUrl('validCode')).toEqual('quiet://?code=validCode') + expect( + invitationDeepUrl([ + { peerId: 'peerID1', onionAddress: 'address1' }, + { peerId: 'peerID2', onionAddress: 'address2' }, + ]) + ).toEqual('quiet://?peerID1=address1&peerID2=address2') + }) + + it('creates invitation share url based on invitation pairs', () => { + const pairs = [ + { peerId: 'peerID1', onionAddress: 'address1' }, + { peerId: 'peerID2', onionAddress: 'address2' }, + ] + const expected = `${QUIET_JOIN_PAGE}#peerID1=address1&peerID2=address2` + expect(pairsToInvitationShareUrl(pairs)).toEqual(expected) }) it('builds proper invitation share url', () => { - expect(invitationShareUrl('validCode')).toEqual(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#validCode`) + const peerList = [ + '/dns4/gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE', + 'invalidAddress', + '/dns4/somethingElse.onion/tcp/443/wss/p2p/QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA', + ] + expect(invitationShareUrl(peerList)).toEqual( + `${QUIET_JOIN_PAGE}#QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSE=gloao6h5plwjy4tdlze24zzgcxll6upq2ex2fmu2ohhyu4gtys4nrjad&QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLsbKSA=somethingElse` + ) + }) + + it('retrieves invitation codes from deep url', () => { + const codes = retrieveInvitationCode(`quiet://?${peerId1}=${address1}&${peerId2}=${address2}`) + expect(codes).toEqual([ + { peerId: peerId1, onionAddress: address1 }, + { peerId: peerId2, onionAddress: address2 }, + ]) + }) + + it('retrieves invitation codes from deep url with partly invalid codes', () => { + const peerId2 = 'QmZoiJNAvCffeEHBjk766nLuKVdkxkAT7wfFJDPPLs' + const address2 = 'y7yczmugl2tekami7sbdz5pfaemvx7bahwthrdvcbzw5vex2crsr26qd' + const codes = retrieveInvitationCode(`quiet://?${peerId1}=${address1}&${peerId2}=${address2}}`) + expect(codes).toEqual([{ peerId: peerId1, onionAddress: address1 }]) }) }) diff --git a/packages/common/src/invitationCode.ts b/packages/common/src/invitationCode.ts index 6d17e38251..e5e6af26f7 100644 --- a/packages/common/src/invitationCode.ts +++ b/packages/common/src/invitationCode.ts @@ -1,46 +1,132 @@ -import { InvitationParams, Site } from './static' - -export const retrieveInvitationCode = (url: string): string => { +import { InvitationPair } from '@quiet/types' +import { ONION_ADDRESS_REGEX, Site } from './static' +import { createLibp2pAddress } from './libp2p' +export const retrieveInvitationCode = (url: string): InvitationPair[] => { /** - * Extract invitation code from deep url. - * Valid format: quiet://?code= + * Extract invitation codes from deep url. + * Valid format: quiet://?=&= */ let data: URL try { data = new URL(url) } catch (e) { - return '' + return [] } - if (!data || data.protocol !== 'quiet:') return '' - const code = data.searchParams.get(InvitationParams.CODE) - if (code) { - console.log('Retrieved code:', code) - return code + if (!data || data.protocol !== 'quiet:') return [] + const params = data.searchParams + const codes: InvitationPair[] = [] + for (const [peerId, onionAddress] of params.entries()) { + if (!invitationCodeValid(peerId, onionAddress)) continue + codes.push({ + peerId, + onionAddress, + }) } - return '' + console.log('Retrieved codes:', codes) + return codes } -export const argvInvitationCode = (argv: string[]): string => { +export const invitationShareUrl = (peers: string[] = []): string => { /** - * Extract invitation code from deep url if url is present in argv + * @arg {string[]} peers - List of peer's p2p addresses + * @returns {string} - Complete shareable invitation link, e.g. https://tryquiet.org/join/#=&= */ - let invitationCode = '' - for (const arg of argv) { - invitationCode = retrieveInvitationCode(arg) - if (invitationCode) { - break + console.log('Invitation share url, peers:', peers) + const pairs = [] + for (const peerAddress of peers) { + let peerId: string + let onionAddress: string + try { + peerId = peerAddress.split('/p2p/')[1] + } catch (e) { + console.info(`Could not add peer address '${peerAddress}' to invitation url. Reason: ${e.message}`) + continue + } + try { + onionAddress = peerAddress.split('/tcp/')[0].split('/dns4/')[1] + } catch (e) { + console.info(`Could not add peer address '${peerAddress}' to invitation url. Reason: ${e.message}`) + continue } + + if (!peerId || !onionAddress) { + console.error(`No peerId or address in ${peerAddress}`) + continue + } + const rawAddress = onionAddress.endsWith('.onion') ? onionAddress.split('.')[0] : onionAddress + pairs.push(`${peerId}=${rawAddress}`) + } + + console.log('invitationShareUrl', pairs.join('&')) + const url = new URL(`${Site.MAIN_PAGE}${Site.JOIN_PAGE}#${pairs.join('&')}`) + return url.href +} + +export const pairsToP2pAddresses = (pairs: InvitationPair[]): string[] => { + const addresses: string[] = [] + for (const pair of pairs) { + addresses.push(createLibp2pAddress(pair.onionAddress, pair.peerId)) + } + return addresses +} + +export const pairsToInvitationShareUrl = (pairs: InvitationPair[]) => { + const url = new URL(`${Site.MAIN_PAGE}${Site.JOIN_PAGE}`) + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.onionAddress) } - return invitationCode + return url.href.replace('?', '#') } -export const invitationDeepUrl = (code = ''): string => { +export const invitationDeepUrl = (pairs: InvitationPair[] = []): string => { const url = new URL('quiet://') - url.searchParams.append(InvitationParams.CODE, code) + for (const pair of pairs) { + url.searchParams.append(pair.peerId, pair.onionAddress) + } return url.href } -export const invitationShareUrl = (code = ''): string => { - const url = new URL(`https://${Site.DOMAIN}/${Site.JOIN_PAGE}#${code}`) - return url.href +export const argvInvitationCode = (argv: string[]): InvitationPair[] => { + /** + * Extract invitation codes from deep url if url is present in argv + */ + let invitationCodes: InvitationPair[] = [] + for (const arg of argv) { + invitationCodes = retrieveInvitationCode(arg) + if (invitationCodes.length > 0) { + break + } + } + return invitationCodes +} + +export const invitationCodeValid = (peerId: string, onionAddress: string): boolean => { + if (!peerId.match(/^[a-zA-Z0-9]{46}$/g)) { + // TODO: test it more properly e.g with PeerId.createFromB58String(peerId.trim()) + console.log(`PeerId ${peerId} is not valid`) + return false + } + if (!onionAddress.trim().match(ONION_ADDRESS_REGEX)) { + console.log(`Onion address ${onionAddress} is not valid`) + return false + } + return true +} + +export const getInvitationPairs = (code: string) => { + /** + * @param code =&= + */ + const pairs = code.split('&') + const codes: InvitationPair[] = [] + for (const pair of pairs) { + const [peerId, address] = pair.split('=') + if (!peerId || !address) continue + if (!invitationCodeValid(peerId, address)) continue + codes.push({ + peerId: peerId, + onionAddress: address, + }) + } + return codes } diff --git a/packages/backend/src/nest/libp2p/libp2p.utils.ts b/packages/common/src/libp2p.ts similarity index 73% rename from packages/backend/src/nest/libp2p/libp2p.utils.ts rename to packages/common/src/libp2p.ts index ecb72160b1..e86ee508e2 100644 --- a/packages/backend/src/nest/libp2p/libp2p.utils.ts +++ b/packages/common/src/libp2p.ts @@ -2,10 +2,10 @@ const ONION = '.onion' export const createLibp2pAddress = (address: string, peerId: string) => { if (!address.endsWith(ONION)) address += ONION - return `/dns4/${address}/tcp/443/wss/p2p/${peerId}` + return `/dns4/${address}/tcp/80/ws/p2p/${peerId}` } export const createLibp2pListenAddress = (address: string) => { if (!address.endsWith(ONION)) address += ONION - return `/dns4/${address}/tcp/443/wss` + return `/dns4/${address}/tcp/80/ws` } diff --git a/packages/common/src/static.ts b/packages/common/src/static.ts index 057018d0a7..05b1f35f0a 100644 --- a/packages/common/src/static.ts +++ b/packages/common/src/static.ts @@ -1,11 +1,9 @@ export const ONION_ADDRESS_REGEX = /^[a-z0-9]{56}$/g -export enum InvitationParams { - CODE = 'code', -} - export enum Site { DOMAIN = 'tryquiet.org', MAIN_PAGE = 'https://tryquiet.org/', JOIN_PAGE = 'join', } + +export const QUIET_JOIN_PAGE = `${Site.MAIN_PAGE}${Site.JOIN_PAGE}` diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json index 4c2fe1bc29..b889234c8b 100644 --- a/packages/common/tsconfig.build.json +++ b/packages/common/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.build.json", "compilerOptions": { + "target": "ES2020", "rootDir": "./src", "outDir": "./lib", "typeRoots": [ diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index afd02831cc..6adb0ef8de 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2017", + "target": "ES2020", "outDir": "./lib", "typeRoots": [ "../@types", diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md index 3965416a19..c63622b9fe 100644 --- a/packages/desktop/CHANGELOG.md +++ b/packages/desktop/CHANGELOG.md @@ -3,6 +3,118 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# 2.0.0-alpha.18 (2023-10-04) + + +### Bug Fixes + +* add conditional checksum path ([8164b50](https://github.com/ZbayApp/monorepo/commit/8164b50774f32856d9886f75d1bb25788f1e03c4)) + + +### Reverts + +* Revert "Remove afterAllArtifactBuild for linux" ([975d0df](https://github.com/ZbayApp/monorepo/commit/975d0df58494bdfba1270f6845152af4969e77ea)) + + + + + +# [2.0.0-alpha.10](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.9...quiet@2.0.0-alpha.10) (2023-09-19) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.9](https://github.com/TryQuiet/quiet/compare/quiet@1.9.1...quiet@2.0.0-alpha.9) (2023-09-18) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.8](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.7...quiet@2.0.0-alpha.8) (2023-09-14) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.7](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.6...quiet@2.0.0-alpha.7) (2023-09-07) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.6](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.5...quiet@2.0.0-alpha.6) (2023-09-07) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.5](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.4...quiet@2.0.0-alpha.5) (2023-09-06) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.4](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.3...quiet@2.0.0-alpha.4) (2023-09-06) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.3](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.2...quiet@2.0.0-alpha.3) (2023-09-05) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.2](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.1...quiet@2.0.0-alpha.2) (2023-09-05) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.1](https://github.com/TryQuiet/quiet/compare/quiet@2.0.0-alpha.0...quiet@2.0.0-alpha.1) (2023-09-01) + +**Note:** Version bump only for package quiet + + + + + +# [2.0.0-alpha.0](https://github.com/TryQuiet/quiet/compare/quiet@1.10.0-alpha.0...quiet@2.0.0-alpha.0) (2023-09-01) + +**Note:** Version bump only for package quiet + + + + + +# [1.10.0-alpha.0](/compare/quiet@1.9.0...quiet@1.10.0-alpha.0) (2023-08-29) + +**Note:** Version bump only for package quiet + + + + + ## [1.9.4](https://github.com/TryQuiet/quiet/compare/quiet@1.9.3...quiet@1.9.4) (2023-10-03) **Note:** Version bump only for package quiet diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 5dbf233bcf..c58c4b3d5c 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -2,7 +2,7 @@ Running the desktop version of Quiet should be straightforward on Mac, Windows, and Linux. Here are the steps: -0. Use `Node 18.12.1` and `npm 8.19.2`. We recommend [nvm](https://github.com/nvm-sh/nvm) for easily switching Node versions, and if this README gets out of date you can see the actual version used by CI [here](https://github.com/TryQuiet/quiet/blob/master/.github/actions/setup-env/action.yml). +0. Use `Node 18.12.1` and `npm 8.19.2`. We recommend [nvm](https://github.com/nvm-sh/nvm) for easily switching Node versions, and if this README gets out of date you can see the actual version used by CI [here](https://github.com/TryQuiet/quiet/blob/master/.github/actions/setup-env/action.yml). If you are using nvm, you can run `nvm use` in the project's root to switch to the correct version. 1. In `quiet/` (project's root) install monorepo's dependencies and bootstrap the project with lerna. It will take care of the package's dependencies and trigger a prepublish script which builds them. ``` @@ -22,7 +22,7 @@ npm run start ## Versioning packages -Before trying to release a new version, make sure you have GH_TOKEN env set. +Before trying to release a new version, make sure you have `GH_TOKEN` env set. The project uses independent versioning which means each package has its own version number. Only those packages in which something has changed since the last release will be bumped. @@ -60,7 +60,7 @@ npm run lerna add luxon packages/state-manager ---- -Lerna takes care of all the packages. You can execute scripts is every pakcage by simpy running: +Lerna takes care of all the packages. You can execute scripts is every package by simply running: ``` npm run lerna run