diff --git a/.dockerignore b/.dockerignore index 205d1cc84..21c4a717b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,22 +1,33 @@ # https://github.com/docker/hub-feedback/issues/1843 ** !download_content.sh -!backend/requirements.txt -!backend/manage.py -!backend/chat -!backend/templates +!newback/package.json +!newback/nest-cli.json +!newback/yarn.lock +!newback/tsconfig.build.json +!newback/tsconfig.json +!newback/src +!kubernetes/docker/backup/backup.sh +!common/src +!kubernetes !frontend/src !frontend/patches !frontend/build +frontend/build/production.json !frontend/package.json +!backend/requirements.txt +!backend/chat +!backend/templates +!backend/manage.py !frontend/yarn.lock !frontend/tsconfig.json !frontend/tsconfig.node.json !docker/populate !docker/tornado -!kubernetes/frontend-old-pychat.org.json -!kubernetes/nginx-old-pychat.org.conf -!kubernetes/frontend-minikube.json +!kubernetes/docker/frontend/frontend-old-pychat.org.json +!kubernetes/docker/frontend/nginx-old-pychat.org.conf +!kubernetes/docker/frontend/frontend-minikube.json +!kubernetes/docker/docker/postfix/postfix_replacer.bash !docker/nginx.conf !docker/nginx !docker/pychat.org/certificate.crt diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml deleted file mode 100644 index b395fe35e..000000000 --- a/.github/workflows/backend.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: BE:pychat.org - -on: - push: - branches: [ master ] - paths: - - 'backend/**' - -jobs: - update_backend: - runs-on: ubuntu-latest - - steps: - - name: Setup ssh - run: | - mkdir -p ~/.ssh - echo "${{ secrets.ID_RSA }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -p ${{ secrets.PORT }} ${{ secrets.HOST }} >> ~/.ssh/known_hosts - - name: Update master branch - run: ssh ${{ secrets.SSH_USER }}@${{ secrets.HOST }} -p ${{ secrets.PORT }} "cd /srv/http/pychat/backend && git pull" - - name: Update database - run: ssh ${{ secrets.SSH_USER }}@${{ secrets.HOST }} -p ${{ secrets.PORT }} "cd /srv/http/pychat/backend && ./.venv/bin/python ./manage.py makemigrations && ./.venv/bin/python ./manage.py migrate" - - name: Restart tornado service - run: ssh ${{ secrets.SSH_USER }}@${{ secrets.HOST }} -p ${{ secrets.PORT }} "sudo /usr/bin/systemctl restart tornado" diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml deleted file mode 100644 index 6882a8a13..000000000 --- a/.github/workflows/frontend.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: FE:pychat.org - -on: - push: - branches: [ master ] - paths: - - 'frontend/**' - -jobs: - upload_frontend: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta - with: - node-version: '14.17' - - name: Cache multiple paths - uses: actions/cache@v2 - with: - path: | - frontend/node_modules - frontend/yarn-cache - key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} - - name: Install npm dependencies - run: | - cd frontend - yarn config set cache-folder `pwd`/yarn-cache - yarn install --frozen-lockfile - - name: Build static files - run: cd frontend && mv build/pychat.org.json build/production.json && yarn build - - - name: Setup ssh - run: | - mkdir -p ~/.ssh - echo "${{ secrets.ID_RSA }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -p ${{ secrets.PORT }} ${{ secrets.HOST }} >> ~/.ssh/known_hosts - - name: Copy static files to destination server - run: | - cd frontend - tar c dist/ | ssh ${{ secrets.SSH_USER }}@${{ secrets.HOST }} -p ${{ secrets.PORT }} "rm -rf /srv/http/pychat/frontend/dist && tar x -C /srv/http/pychat/frontend/" diff --git a/.github/workflows/newback.yml b/.github/workflows/newback.yml new file mode 100644 index 000000000..382ab0ac6 --- /dev/null +++ b/.github/workflows/newback.yml @@ -0,0 +1,38 @@ +name: NEWBE:pychat.org + +on: + push: + branches: [ master, nestjs ] + paths: + - 'newback/**' + +jobs: + test-backend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2-beta + with: + node-version: 'v16.15' + - name: Cache multiple paths + uses: actions/cache@v2 + with: + path: | + newback/node_modules + newback/yarn-cache + newback/codecov + key: ${{ runner.os }}-${{ hashFiles('newback/yarn.lock') }} + - name: Install npm dependencies + run: | + cd newback + yarn config set cache-folder `pwd`/yarn-cache + yarn install --frozen-lockfile + - name: Run tests + run: cd newback && yarn jest --config ./src/utils/jest.config.ts --runInBand --coverage --detectOpenHandles --coverageReporters json text + - name: Coverage + run: | + cd newback + ls ./codecov || curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x ./codecov + ./codecov -t $CODECOV_TOKEN -f ./coverage/coverage-final.json + diff --git a/.excludeMAIN b/.gitignore similarity index 64% rename from .excludeMAIN rename to .gitignore index de0cd6e65..24324161e 100644 --- a/.excludeMAIN +++ b/.gitignore @@ -1,25 +1,17 @@ -.gitBU -.excludeBU .project .pydevproject .idea -md5 -md5-wasm-example test/ -dump.rdb -backup.sh -data.sql -cron-bu -trash -README.txt rootfs/etc/nginx/ssl/* +backend/data.sql rootfs/etc/nginx/ssl/server.key rootfs/etc/nginx/ssl/certificate.key backend/chat/migrations/* backend/chat/production.py backend/chat/settings.py backend/photos/* -kubernetes/cf-secret.yaml -kubernetes/oldbackend-secret.yaml !backend/photos/.gitkeep +newback/photos/* +!newback/photos/.gitkeep frontend/build/production.json +newback/src/config/env/default.json diff --git a/README.md b/README.md index 9927c9236..60aa15be9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -[![docker](https://github.com/akoidan/pychat/actions/workflows/docker.yml/badge.svg?branch=master)](https://github.com/akoidan/pychat/actions/workflows/docker.yml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Deathangel908/pychat/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Deathangel908/pychat/?branch=master) [![Upload Frontend pychat.org](https://github.com/akoidan/pychat/workflows/FE:pychat.org/badge.svg?branch=master)](https://github.com/akoidan/pychat/actions?query=workflow%3AFE%3Apychat.org) [![Refresh backend pychat.org](https://github.com/akoidan/pychat/workflows/BE:pychat.org/badge.svg?branch=master)](https://github.com/akoidan/pychat/actions?query=workflow%3ABE%3Apychat.org) - +[![Docker](https://github.com/akoidan/pychat/actions/workflows/docker.yml/badge.svg?branch=master)](https://github.com/akoidan/pychat/actions/workflows/docker.yml) +[![Newbackend test](https://github.com/akoidan/pychat/workflows/NEWBE:pychat.org/badge.svg)](https://github.com/akoidan/pychat/actions) +[![Codecov](https://codecov.io/gh/akoidan/pychat/branch/nestjs/graph/badge.svg?token=aQlNwbpTIw)](https://codecov.io/gh/akoidan/pychat) # Live demo: [pychat.org](https://pychat.org/), [video](https://www.youtube.com/watch?v=m6sJ-blTidg) ## Table of contents @@ -260,7 +261,6 @@ Useful links: Remember that Service Worker will work only if certificate is trusted. So flags like ignore-ceritifcate-errors won't work. But installing certifcate to root system will. ## Bootstrap files: - 1. I use 2 git repos in 2 project directory. So you probably need to rename `excludeMAIN`file to `.gitignore`or create link to exclude. `ln -rsf .excludeMAIN .git/info/exclude` 1. Rename [backend/chat/settings_example.py](backend/chat/settings_example.py) to `backend/chat/settings.py`. **Modify file according to the comments in it.** 1. Install python packages with `pip install -r requirements.txt`. (Remember you're still in `backend` dir) 1. From **root** (sudo) user create the database (from shell environment): `echo "create database pychat CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; CREATE USER 'pychat'@'localhost' identified by 'pypass'; GRANT ALL ON pychat.* TO 'pychat'@'localhost';" | mysql -u root`. You will need mysql running for that (e.g. `systemctl start mysql` on archlinux) If you also need remote access do the same with `'192.168.1.0/255.255.255.0';` @@ -510,7 +510,149 @@ Cmnd_Alias RESTART_TORNADO = /usr/bin/systemctl restart tornado http ALL=(ALL) NOPASSWD: RESTART_TORNADO ``` + +# Terraform +Terraform will allow you to automatically create pychat infrastructure for: + - Cloudflare as CDN and DNS (free of charge) + - Linode as Kubernetes provider. It has the cheapest prices (10$ per month is gonna be enough, that will use 1 node of 2GB ram and 1vCPU) along all providers with 500MB/s SSD, 99.99% SLA, 10Gb/s in and 2Gb/s out networks. + - Letsencrypt as SSL certificate (free of charge) + - Domain address (10$ per year) +You will have to create a few accounts and setup a few variables in ./kubernetes/terraform/terraform.tfvars below + +### Cloudflare + - Sign in to [Cloudflare](https://www.cloudflare.com/) and copy ZoneID and API token + - Copy zoneID: Go to https://dash.cloudflare.com/ - create a new site (or use existing one) grab an API Zone ID and place it into `kubernetes/terraform.tfvars` with key `cloud_flare_zone_id` + - Copy API token: go to https://dash.cloudflare.com/profile/api-tokens and create a new api token and place it into `kubernetes/terraform.tfvars` with key `cloud_flare_api_token` + +### Linode + Sign in to [Linode](http://linode.com/), go to Profile -> Api tokens -> Create A Personal Access Token -> Select Kubernetes & Linodes -> Create Token. Put that token to`kubernetes/terraform.tfvars` with key `linode_token` + +### Backups: +Backups are done using private git repository. It includes sql dump + photo directory + - Create a private repo. + - Place repo url e.g. `git@github.com:username/backup_repo.git` to `terraform.tfvars` with key `github`. Url should be in ssh format not in https. Authorization will be done by ssh key-pair + - Generate a new keypair with `ssh-keygen -t rsa -b 4096 -C "kube backup" -f /tmp/sshkey/id_rsa -N ""` + - Put public key to your git server (for Github it's Settings (on dashboard) -> Deploy Keys -> Add deploy key) + - Put ssh public key `ssh-rsa aa...` to `terraform.tfvars` with key `id_rsa_pub` + - Put private key `-----BEGIN OPENSSH PRIVATE KEY-----... -----END OPENSSH PRIVATE KEY--` to `terraform.tfvars` with key `id_rsa`. You can use [this](https://stackoverflow.com/a/66646420/3872976) lifehack for multiline variable +k8s wil have a cronjob backend-backup which will run each hour. + +### Other keys +Other keys are required to be specified in `terraform.tfvars` + - put `domain_name` + +### Self sign certificate (Optional): +Skip this if you did cloudflare step. +If you don't own a domain (and planning to use ip + self signed certificate, this will result: +- certmanager will not deploy be used +- turn server will not work, +- postfix will not send emails, including registration and password reset +- Site is gonna be under self-signed certificate and will prompt errors. +This is still possible +- set `use_certmanager = false` in `terraform.tfvars` +Upon linode cluster creation .kubeconfig will be generated in kubernetes/terraform/helm directory. After it all operations with helm will be using this conf. If this file is deleted helm will compain about missing kubernetes config + +### Other variables in terraform.tfvars + - domain_name - your host origin domain name. It's gonna be used in turnserver postfix static nginx, cloudflare and other configuration + - email - It's gonna be used in cf issuer and a few other places + - Other variables that's gonna be proxied as env variables for python backend. Check backend/chat/settings.example for docs. Those includes but not limited to DEFAULT_PROFILE_ID SECRET_KEY RECAPTCHA_PRIVATE_KEY RECAPTCHA_PUBLIC_KEY GOOGLE_OAUTH_2_CLIENT_ID FACEBOOK_ACCESS_TOKEN GIPHY_API_KEY FIREBASE_API_KEY + +### If you already own a k8s cluster. +All helm charts are located in kubernetes/terraform/helm, we will apply terraform from this directory to exclude linode and cloudlfare resource creation + - cd kubernetes/terraform/helm + - create `terraform.tfvars` in this directory and put all required content in it. + - `cat ~/.kube/config |base64 -w 0` and place value to `terraform.tfvars` with key `kubeconfig` + - Put external ip address of the k8s node you are going to deploy pychat in `terraform.tfvars` with key `ip_address`. Pychat uses NodePort, if you want to use your loadbalancer you will have to tweak ingress configuration manually. + - `terraform init` + - `terraform apply` + +### Frontend + +Frontend can't be built during runtime, thus you will need to built it yourself. Also you need to have accesss to some docker registry, you can use dockerhub as it's free of charge. This repo also can host private docker registry built from kubernetes/terraform/helm/charts/docker-registry so you won't have to create one.\ +Replace values in `/kubernetes/docker/frontend/frontend-old-pychat.org.json` in and run from project root directory. +```bash +docker build --build-arg PYCHAT_GIT_HASH=`git rev-parse --short=10 HEAD` -f ./kubernetes/docker/frontend/Dockerfile -t deathangel908/pychat-frontend . +docker push deathangel908/pychat-frontend +``` +Note that tag `deathangel908/pychat-frontend` should be one pointing to your registry +Replace value in `repository_path` in `./kubernetes/terraform/helm/charts/frontend/values.yaml` to one pointing to you registry +In case you use private registry from this terraform image use: + - generate user-password with: `docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword` + - put output to `terraform.tfvars` with key `htpasswd` + - Fully apply terraform configuration. Most services could be down + - Login to your private registry with `docker login --username=testuser --password=testpassword registry.pychat.org` + - And do push like above `docker push deathangel908/pychat-frontend` with your tag + - If you kill frontend pod now, it should repull from your repo + +### Apply Terrraform +Apply terraform configuration with: + - `terraform init` + - `terraform plan` + - `terraform apply` + +### Apply helm manually +If you render helm manually, you need to specify `ip_address` of your Node cluster in [terraform.tfvars](kubernetes/terraform/terraform.tfvars) or in [values.yaml](kubernetes/terraform/helm/values.yaml) +You can get it from +```bash +kubectl get nodes -o wide +``` +example of output: +```text +: kubectl get nodes -o wide +Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens. +NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME +lke111154-165479-6474ce21ad26 Ready 7d20h v1.31.0 192.168.175.24 192.46.236.13 Debian GNU/Linux 12 (bookworm) 6.1.0-27-cloud-amd64 containerd://1.7.22 +``` +external IP address here should match with external ip address here +```bash +kubectl get svc -n ingress-nginx +``` +example of output: +```text +: kubectl get svc -n ingress-nginx +Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens. +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +ingress-nginx-controller LoadBalancer 10.128.229.237 192.46.236.13 80:30841/TCP,443:30671/TCP, +``` +It's ok so it uses extra node ports + +Also node IP address should be specified in DNS as well as in SPF email protection settings + +```bash +cd ./kubernetes/terraform/helm +``` +create values.yaml in this directory based on **kubernetes/terraform/helm/variables.tf** and **kubernetes/terraform/terraform.tfvars** +Render tempalte with +```bash +helm template ingress charts/ingress -f values.yaml +``` +If your kubectl is connected to the proper cluster, you can apply it with: +apply with +```bash +helm template ingress charts/ingress -f values.yaml |kubectl apply -f - +``` + +### Troubleshooting +1. How to icrease maximum upload file: + - Change nginx configuration `` client_max_body_size 75M;` + - In addition to above, if you're using k8s + ingress, change `ingress.yaml` nginx.ingress.kubernetes.io/proxy-body-size: 75m + +1. Certmanager fails or services that depends on SSL won't start (e.g. Postfix or Coturn) +Certmanager should create pychat_tls sercret in namespace pychat. It creates a private key and puts it into letsencrypt-prod secret. Then it tries to issue it against letsencrypt server. + - Check the logs of the certmanager pods. + - Note that letsencrypt allows you max 5 cetrificates in 160 hours. If you used all tries, use another domain or subdomain + +1. Terraform apply produces errors like `Unexpected EOF`. + - Seems like your k8s providers API exceed limits and it cuts the k8s request. You can try it with `terraform apply -parallelism=1`. Your cluster will deploy for a while, but it has less chance to get into this situation + - You can also execute `terraform apply` a few times so it finishes creating resources that it failed to inspect. Note that if you got a certificate already, I recomend you to back it up from `pychat-tls` secret in order to avoid cetrification fails describe above + +1. Cloudflare returns too many redirect within the same time If turn it off (by setting Node's ip to /etc/hosts) it works fine. This result when using [flexible mode](https://developers.cloudflare.com/ssl/troubleshooting/too-many-redirects/#encryption-mode-misconfigurations). Since all request from http will be redirected to https, all http urls will return header location: https since result infinitive loop + - Go to cloudlfare ssl/tls overview + - Set strict encryption mode + - Go to Caching Configuration. Click on Custom purge and select static.pychat.org (or static.yourmdain.com) or Purge EVerything if pychat is the only domain. # TODO +* Move flags to json probably and JSON.parse so it takes less compile time +* Check chrome dev tools and Coverage (the tab is on the same spot as Console (for logs)). If not visible check 3 dots on the left to Console tab. https://www.youtube.com/watch?v=X9eRLElSW1c&t=1720&ab_channel=estellevw * teleport smileys https://vuejsdevelopers.com/2020/03/16/vue-js-tutorial/#teleporting-content * user1 writes a message, user1 goes offline, user 2 opens a chat from 1st devices and goes offline, user 2 opens a chat from 2nd devices and responds in its thread and goes offline, user2 opens first deviecs and thread messages count = 0 * loading messages is too slow, when a lot of messages is printed to local database. It's better to load 20 last messages to chat instead of 100000, probably vuex getter but it's better to think what we should do on scroll. diff --git a/backend/templates/sign_up_email.html b/backend/templates/sign_up_email.html index 2d75d6399..231574ba1 100644 --- a/backend/templates/sign_up_email.html +++ b/backend/templates/sign_up_email.html @@ -1,5 +1,6 @@

Hello, {{ username }}!

-

{{ greetings }}

+

You have registered in Pychat. If you find any bugs or propositions you can post them + here. To complete your registration please click on the link below.

Hello, {{ username }}! font-weight: normal; text-decoration: none; word-break: break-word">{{ magicLink }} -
\ No newline at end of file + diff --git a/common/.eslintrc.json b/common/.eslintrc.json new file mode 120000 index 000000000..e57bc20c2 --- /dev/null +++ b/common/.eslintrc.json @@ -0,0 +1 @@ +../newback/.eslintrc.json \ No newline at end of file diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 000000000..de4d1f007 --- /dev/null +++ b/common/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/common/.nvmrc b/common/.nvmrc new file mode 100644 index 000000000..04c9e2bf6 --- /dev/null +++ b/common/.nvmrc @@ -0,0 +1 @@ +v16.15 diff --git a/common/package.json b/common/package.json new file mode 100644 index 000000000..df7f33c97 --- /dev/null +++ b/common/package.json @@ -0,0 +1,29 @@ +{ + "name": "pychat-common", + "version": "1.0.0", + "description": "Opensource chat", + "author": "Andrew Koidan =16.0.0 <17" + }, + "dependencies": { + "lines-logger": "^2.1.2", + "typescript": "^4.6.3", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "eslint": "^8.12.0", + "eslint-find-rules": "^4.1.0", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.4", + "eslint-plugin-only-warn": "^1.0.3", + "eslint-plugin-sonarjs": "^0.13.0" + } +} diff --git a/common/src/consts.ts b/common/src/consts.ts new file mode 100644 index 000000000..88ea6943c --- /dev/null +++ b/common/src/consts.ts @@ -0,0 +1,3 @@ +export const WS_SESSION_EXPIRED_CODE = 4001; +export const ALL_ROOM_ID = 1; +export const MAX_USERNAME_LENGTH = 16; diff --git a/common/src/http/auth/facebook.sign.in.ts b/common/src/http/auth/facebook.sign.in.ts new file mode 100644 index 000000000..fe084d890 --- /dev/null +++ b/common/src/http/auth/facebook.sign.in.ts @@ -0,0 +1,7 @@ +import {OauthSessionResponse} from "@common/model/http.base"; + +export type FacebookSignInResponse = OauthSessionResponse; + +export interface FaceBookSignInRequest { + token: string; +} diff --git a/common/src/http/auth/google.sign.in.ts b/common/src/http/auth/google.sign.in.ts new file mode 100644 index 000000000..b7e2a11f1 --- /dev/null +++ b/common/src/http/auth/google.sign.in.ts @@ -0,0 +1,7 @@ +import {OauthSessionResponse} from "@common/model/http.base"; + +export type GoogleSignInResponse = OauthSessionResponse; + +export interface GoogleSignInRequest { + token: string; +} diff --git a/common/src/http/auth/sign.in.ts b/common/src/http/auth/sign.in.ts new file mode 100644 index 000000000..fbf973f06 --- /dev/null +++ b/common/src/http/auth/sign.in.ts @@ -0,0 +1,12 @@ +import { + CaptchaRequest, + SessionResponse +} from "@common/model/http.base"; + +export type SignInResponse = SessionResponse; + +export interface SignInRequest extends CaptchaRequest { + username?: string; + password: string; + email?: string; +} diff --git a/common/src/http/auth/sign.up.ts b/common/src/http/auth/sign.up.ts new file mode 100644 index 000000000..085570fa5 --- /dev/null +++ b/common/src/http/auth/sign.up.ts @@ -0,0 +1,11 @@ +import type {Gender} from "@common/model/enum/gender"; +import {SessionResponse} from "@common/model/http.base"; + +export type SignUpResponse = SessionResponse; + +export interface SignUpRequest { + username: string; + password: string; + email?: string; + sex?: Gender; +} diff --git a/common/src/http/auth/validate.user.ts b/common/src/http/auth/validate.user.ts new file mode 100644 index 000000000..f87a92f10 --- /dev/null +++ b/common/src/http/auth/validate.user.ts @@ -0,0 +1,7 @@ +import {OkResponse} from "@common/model/http.base"; + +export interface ValidateUserRequest { + username: string; +} + +export type ValidateUserResponse = OkResponse; diff --git a/common/src/http/auth/validte.email.ts b/common/src/http/auth/validte.email.ts new file mode 100644 index 000000000..75db4cc44 --- /dev/null +++ b/common/src/http/auth/validte.email.ts @@ -0,0 +1,7 @@ +import {OkResponse} from "@common/model/http.base"; + +export type ValidateEmailResponse = OkResponse; + +export interface ValidateUserEmailRequest { + email: string; +} diff --git a/common/src/http/file/save.file.ts b/common/src/http/file/save.file.ts new file mode 100644 index 000000000..50dafbdee --- /dev/null +++ b/common/src/http/file/save.file.ts @@ -0,0 +1,14 @@ +import type {ImageType} from "@common/model/enum/image.type"; + +export interface SaveFileResponse { + id: number; + previewId?: number; + symbol: string; +} + +export interface SaveFileRequest { + file: Blob; + name?: string; + symbol: string; + type: ImageType; +} diff --git a/common/src/http/verify/accept.token.ts b/common/src/http/verify/accept.token.ts new file mode 100644 index 000000000..f734ca4a4 --- /dev/null +++ b/common/src/http/verify/accept.token.ts @@ -0,0 +1,8 @@ +import {SessionResponse} from "@common/model/http.base"; + +export type AcceptTokenResponse = SessionResponse; + +export interface AcceptTokenRequest { + token: string; + password: string; +} diff --git a/common/src/http/verify/confirm.email.ts b/common/src/http/verify/confirm.email.ts new file mode 100644 index 000000000..684005314 --- /dev/null +++ b/common/src/http/verify/confirm.email.ts @@ -0,0 +1,7 @@ +import {OkResponse} from "@common/model/http.base"; + +export interface ConfirmEmailRequest { + token: string; +} + +export type ConfirmEmailResponse = OkResponse; diff --git a/common/src/http/verify/send.restore.password.ts b/common/src/http/verify/send.restore.password.ts new file mode 100644 index 000000000..19469e014 --- /dev/null +++ b/common/src/http/verify/send.restore.password.ts @@ -0,0 +1,11 @@ +import { + CaptchaRequest, + OkResponse +} from "@common/model/http.base"; + +export type SendRestorePasswordResponse = OkResponse; + +export interface SendRestorePasswordRequest extends CaptchaRequest { + username?: string; + email?: string; +} diff --git a/common/src/http/verify/verify.token.ts b/common/src/http/verify/verify.token.ts new file mode 100644 index 000000000..6f4537910 --- /dev/null +++ b/common/src/http/verify/verify.token.ts @@ -0,0 +1,9 @@ +import {OkResponse} from "@common/model/http.base"; + +export interface VerifyTokenRequest { + token: string; +} + +export interface VerifyTokenResponse extends OkResponse { + username: string; +} diff --git a/common/src/model/dto/channel.dto.ts b/common/src/model/dto/channel.dto.ts new file mode 100644 index 000000000..e3b3a512f --- /dev/null +++ b/common/src/model/dto/channel.dto.ts @@ -0,0 +1,5 @@ +export interface ChannelDto { + name: string; + id: number; + creatorId: number; +} diff --git a/common/src/model/dto/file.model.dto.ts b/common/src/model/dto/file.model.dto.ts new file mode 100644 index 000000000..6190c58d5 --- /dev/null +++ b/common/src/model/dto/file.model.dto.ts @@ -0,0 +1,8 @@ +import type {ImageType} from "@common/model/enum/image.type"; + +export interface FileModelDto { + url: string; + id: number; + type: ImageType; + preview: string; +} diff --git a/common/src/model/dto/giphy.dto.ts b/common/src/model/dto/giphy.dto.ts new file mode 100644 index 000000000..31f7ded03 --- /dev/null +++ b/common/src/model/dto/giphy.dto.ts @@ -0,0 +1,5 @@ +export interface GiphyDto { + webp: string; + url: string; + symbol: string; +} diff --git a/common/src/model/dto/location.dto.ts b/common/src/model/dto/location.dto.ts new file mode 100644 index 000000000..cba899bcf --- /dev/null +++ b/common/src/model/dto/location.dto.ts @@ -0,0 +1,6 @@ +export interface LocationDto { + city: string; + country: string; + countryCode: string; + region: string; +} diff --git a/common/src/model/dto/message.model.dto.ts b/common/src/model/dto/message.model.dto.ts new file mode 100644 index 000000000..69414ec0f --- /dev/null +++ b/common/src/model/dto/message.model.dto.ts @@ -0,0 +1,18 @@ +import type {FileModelDto} from "@common/model/dto/file.model.dto"; +import type {MessageStatus} from "@common/model/enum/message.status"; + +export interface MessageModelDto { + id: number; + time: number; + parentMessage: number; + files: Record; + tags: Record; + content: string; + status: MessageStatus; + symbol: string | null; + deleted: boolean; + threadMessagesCount: number; + edited: number; + roomId: number; + userId: number; +} diff --git a/common/src/model/dto/room.dto.ts b/common/src/model/dto/room.dto.ts new file mode 100644 index 000000000..7420c2581 --- /dev/null +++ b/common/src/model/dto/room.dto.ts @@ -0,0 +1,14 @@ +export interface RoomNoUsersDto { + channelId: number | null; + notifications: boolean; + p2p: boolean; + volume: number; + isMainInChannel: boolean; + id: number; + name: string; + creatorId: number; +} + +export interface RoomDto extends RoomNoUsersDto { + users: number[]; +} diff --git a/common/src/model/dto/user.dto.ts b/common/src/model/dto/user.dto.ts new file mode 100644 index 000000000..547368d08 --- /dev/null +++ b/common/src/model/dto/user.dto.ts @@ -0,0 +1,9 @@ +import type {Gender} from "@common/model/enum/gender"; + +export interface UserDto { + username: string; + id: number; + thumbnail: string; + lastTimeOnline: number; + sex: Gender; +} diff --git a/common/src/model/dto/user.profile.dto.ts b/common/src/model/dto/user.profile.dto.ts new file mode 100644 index 000000000..31e3e853c --- /dev/null +++ b/common/src/model/dto/user.profile.dto.ts @@ -0,0 +1,17 @@ +import type {Gender} from "@common/model/enum/gender"; + +export interface UserProfileDtoWoImage { + username: string; + name: string; + city: string; + surname: string; + email: string; + birthday: Date; + contacts: string; + sex: Gender; + id: number; +} + +export interface UserProfileDto extends UserProfileDtoWoImage { + thumbnail: string; +} diff --git a/common/src/model/dto/user.settings.dto.ts b/common/src/model/dto/user.settings.dto.ts new file mode 100644 index 000000000..d29f327c8 --- /dev/null +++ b/common/src/model/dto/user.settings.dto.ts @@ -0,0 +1,13 @@ +import type {LogLevel} from "lines-logger"; + +export interface UserSettingsDto { + embeddedYoutube: boolean; + highlightCode: boolean; + incomingFileCallSound: boolean; + messageSound: boolean; + onlineChangeSound: boolean; + showWhenITyping: boolean; + suggestions: boolean; + logs: LogLevel; + theme: string; +} diff --git a/common/src/model/enum/gender.ts b/common/src/model/enum/gender.ts new file mode 100644 index 000000000..d98d588fd --- /dev/null +++ b/common/src/model/enum/gender.ts @@ -0,0 +1,6 @@ +// ISO/IEC 5218 1 male, 2 - female +export enum Gender { + MALE = "MALE", + FEMALE = "FEMALE", + OTHER = "OTHER", +} diff --git a/common/src/model/enum/image.type.ts b/common/src/model/enum/image.type.ts new file mode 100644 index 000000000..d20b094a5 --- /dev/null +++ b/common/src/model/enum/image.type.ts @@ -0,0 +1,9 @@ +export enum ImageType { + VIDEO = "VIDEO", + FILE = "FILE", + MEDIA_RECORD = "MEDIA_RECORD", + AUDIO_RECORD = "AUDIO_RECORD", + IMAGE = "IMAGE", + PREVIEW = "PREVIEW", + GIPHY = "GIPHY", +} diff --git a/common/src/model/enum/message.status.ts b/common/src/model/enum/message.status.ts new file mode 100644 index 000000000..739bb3660 --- /dev/null +++ b/common/src/model/enum/message.status.ts @@ -0,0 +1,5 @@ +export enum MessageStatus { + ON_SERVER = "ON_SERVER", // Uploaded to server + READ = "READ", + RECEIVED = "RECEIVED", // Sent +} diff --git a/common/src/model/enum/theme.ts b/common/src/model/enum/theme.ts new file mode 100644 index 000000000..47c62e13f --- /dev/null +++ b/common/src/model/enum/theme.ts @@ -0,0 +1,5 @@ +export enum Theme { + COLOR_LOR = "COLOR_LOR", + COLOR_REG = "COLOR_REG", + COLOR_WHITE = "COLOR_WHITE", +} diff --git a/common/src/model/enum/verification.type.ts b/common/src/model/enum/verification.type.ts new file mode 100644 index 000000000..a943e6557 --- /dev/null +++ b/common/src/model/enum/verification.type.ts @@ -0,0 +1,5 @@ +export enum VerificationType { + REGISTER = "REGISTER", // When user sign up with email, we want to confirm that it's his/her email + PASSWORD = "PASSWORD", // When user request a pasword change + EMAIL = "EMAIL", // When user changes an email +} diff --git a/common/src/model/http.base.ts b/common/src/model/http.base.ts new file mode 100644 index 000000000..7fe1ce4a0 --- /dev/null +++ b/common/src/model/http.base.ts @@ -0,0 +1,28 @@ +import {WebRtcDefaultMessage} from "@common/model/webrtc.base"; +import {ResponseWsInMessage} from "@common/ws/common"; + +export interface CaptchaRequest { + captcha?: string; +} + +interface TypeGeneratorForOauth1 extends SessionResponse { + isNewAccount: true; + username: string; +} + +interface TypeGeneratorForOauth2 extends SessionResponse { + isNewAccount: false; +} + +export type OauthSessionResponse = + TypeGeneratorForOauth1 + | TypeGeneratorForOauth2; + +export interface SessionResponse { + session: string; +} + +export interface OkResponse { + ok: true; +} + diff --git a/common/src/model/webrtc.base.ts b/common/src/model/webrtc.base.ts new file mode 100644 index 000000000..2fd5f3622 --- /dev/null +++ b/common/src/model/webrtc.base.ts @@ -0,0 +1,28 @@ +export interface WebRtcDefaultMessage { + connId: string; +} + +export interface OpponentWsId { + opponentWsId: string; +} + +export interface ReplyWebRtc extends WebRtcDefaultMessage, OpponentWsId { + content: BrowserBase; + userId: number; +} + +export type CallStatus = + "accepted" + | "not_inited" + | "received_offer" + | "sent_offer"; + + +export interface BrowserBase { + browser: string; +} + + +export interface WebRtcSetConnectionIdBody extends WebRtcDefaultMessage { + time: number; +} diff --git a/common/src/model/ws.base.ts b/common/src/model/ws.base.ts new file mode 100644 index 000000000..f2d61da3b --- /dev/null +++ b/common/src/model/ws.base.ts @@ -0,0 +1,37 @@ +import {ChannelDto} from "@common/model/dto/channel.dto"; +import {RoomDto} from "@common/model/dto/room.dto"; +import {MessageModelDto} from "@common/model/dto/message.model.dto"; + +export interface ChangeUserOnlineBase { + online: Record; + userId: number; + lastTimeOnline: number; + time: number; +} + +export interface NewRoom { + inviterUserId: number; + time: number; +} + +export interface RoomExistedBefore { + inviteeUserId: number[]; +} + +export interface AddRoomBase extends NewRoom, ChannelDto, RoomDto { +} + +export interface MessagesResponseMessage { + content: MessageModelDto[]; +} + + +export type ChangeDeviceType = + "i_deleted" + | "invited" + | "room_created" + | "someone_joined" + | "someone_left"; +export type ChangeOnlineType = + "appear_online" + | "gone_offline"; diff --git a/common/src/ws/common.ts b/common/src/ws/common.ts new file mode 100644 index 000000000..ea3cc03d4 --- /dev/null +++ b/common/src/ws/common.ts @@ -0,0 +1,43 @@ +// Every message that server sends to a client (browser) should be a structure like this +export interface DefaultWsInMessage { + action: A; + data: D; + handler: H; +} + +export interface DefaultWsOutMessage { + data: D; + action: A; +} + +export interface RequestWsOutMessage extends DefaultWsOutMessage { + cbId: number; +} + +// If sendToServerAndAwait it used +export interface ResponseWsInMessage { + data: D; + cbBySender: string; + cbId: number; +} + +// If sendToServerAndAwait used but also other clients of this user should receive this message +export interface MultiResponseMessage extends ResponseWsInMessage, DefaultWsInMessage { + +} + + +export type HandlerName = + "*" + | "call" + | "notifier" + | "peerConnection:*" + | "room" + | "router" + | "void" + | "webrtc-message" + | "webrtc" + | "webrtcTransfer:*" + | "ws-message" + | "ws"; + diff --git a/common/src/ws/message/get.country.code.ts b/common/src/ws/message/get.country.code.ts new file mode 100644 index 000000000..604628647 --- /dev/null +++ b/common/src/ws/message/get.country.code.ts @@ -0,0 +1,13 @@ +import type { + RequestWsOutMessage, + ResponseWsInMessage, +} from "@common/ws/common"; +import type {LocationDto} from "@common/model/dto/location.dto"; + +export type GetCountryCodeWsOutMessage = RequestWsOutMessage<"getCountryCode", null>; + +export interface GetCountryCodeWsInBody { + userLocation: Record; +} + +export type GetCountryCodeWsInMessage = ResponseWsInMessage; diff --git a/common/src/ws/message/growl.message.ts b/common/src/ws/message/growl.message.ts new file mode 100644 index 000000000..95eff4910 --- /dev/null +++ b/common/src/ws/message/growl.message.ts @@ -0,0 +1,8 @@ +import type {ResponseWsInMessage,} from "@common/ws/common"; + +export interface GrowlWsInBody { + error: string; +} +export interface GrowlWsInMessage extends ResponseWsInMessage { + action: "growlError"; +} diff --git a/common/src/ws/message/peer-connection/destroy.call.connection.ts b/common/src/ws/message/peer-connection/destroy.call.connection.ts new file mode 100644 index 000000000..820ff33ba --- /dev/null +++ b/common/src/ws/message/peer-connection/destroy.call.connection.ts @@ -0,0 +1,16 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage +} from "@common/ws/common"; +import { + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; + + +export interface DestroyCallConnectionWsOutBody extends WebRtcDefaultMessage { + status: "decline" | "hangup"; +} + +export type DestroyCallConnectionWsOutMessage = DefaultWsOutMessage<"destroyCallConnection", DestroyCallConnectionWsOutBody>; +export type DestroyCallConnectionWsInMessage = DefaultWsInMessage<"destroyCallConnection", "peerConnection:*", null>; diff --git a/common/src/ws/message/peer-connection/destroy.file.connection.ts b/common/src/ws/message/peer-connection/destroy.file.connection.ts new file mode 100644 index 000000000..e23519cbf --- /dev/null +++ b/common/src/ws/message/peer-connection/destroy.file.connection.ts @@ -0,0 +1,8 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + + +export interface DestroyFileConnectionWsInBody { + status: "decline" | "success"; +} + +export type DestroyFileConnectionWsInMessage = DefaultWsInMessage<"destroyFileConnection", "peerConnection:*", DestroyFileConnectionWsInBody>; diff --git a/common/src/ws/message/peer-connection/retry.file.ts b/common/src/ws/message/peer-connection/retry.file.ts new file mode 100644 index 000000000..75a43441a --- /dev/null +++ b/common/src/ws/message/peer-connection/retry.file.ts @@ -0,0 +1,3 @@ +import {DefaultWsInMessage} from "@common/ws/common"; + +export type RetryFileWsInMessage = DefaultWsInMessage<"retryFile", "peerConnection:*", null>; diff --git a/common/src/ws/message/peer-connection/send.rtc.data.ts b/common/src/ws/message/peer-connection/send.rtc.data.ts new file mode 100644 index 000000000..05d1ed2df --- /dev/null +++ b/common/src/ws/message/peer-connection/send.rtc.data.ts @@ -0,0 +1,17 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage +} from "@common/ws/common"; +import { + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; + + +export interface SendRtcDataWsInBody extends OpponentWsId, WebRtcDefaultMessage { + content: RTCIceCandidateInit | RTCSessionDescriptionInit | {message: unknown}; +} + +export type SendRtcDataWsInMessage = DefaultWsInMessage<"sendRtcData", "peerConnection:*", SendRtcDataWsInBody>; + +export type SendRtcDataWsOutMessage = DefaultWsOutMessage<"sendRtcData", any> diff --git a/common/src/ws/message/room/add.channel.ts b/common/src/ws/message/room/add.channel.ts new file mode 100644 index 000000000..92dc76139 --- /dev/null +++ b/common/src/ws/message/room/add.channel.ts @@ -0,0 +1,10 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {ChannelDto} from "@common/model/dto/channel.dto"; +import type {RoomDto} from "@common/model/dto/room.dto"; +import {NewRoom} from "@common/model/ws.base"; + +export interface AddChannelWsInBody extends ChannelDto, Omit, NewRoom { + channelUsers: number[]; +} + +export type AddChannelWsInMessage = DefaultWsInMessage<"addChannel", "room", AddChannelWsInBody>; diff --git a/common/src/ws/message/room/add.invite.ts b/common/src/ws/message/room/add.invite.ts new file mode 100644 index 000000000..8a6e906ef --- /dev/null +++ b/common/src/ws/message/room/add.invite.ts @@ -0,0 +1,9 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import { + AddRoomBase, + RoomExistedBefore +} from "@common/model/ws.base"; + +export type AddInviteWsInBody = AddRoomBase | RoomExistedBefore; + +export type AddInviteWsInMessage = DefaultWsInMessage<"addInvite", "room", AddInviteWsInBody>; diff --git a/common/src/ws/message/room/add.online.user.ts b/common/src/ws/message/room/add.online.user.ts new file mode 100644 index 000000000..abafb29e5 --- /dev/null +++ b/common/src/ws/message/room/add.online.user.ts @@ -0,0 +1,9 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {ChangeUserOnlineBase} from "@common/model/ws.base"; + + +export interface AddOnlineUserWsInBody extends ChangeUserOnlineBase { + opponentWsId: string; +} + +export type AddOnlineUserWsInMessage = DefaultWsInMessage<"addOnlineUser", "room", AddOnlineUserWsInBody>; diff --git a/common/src/ws/message/room/add.room.ts b/common/src/ws/message/room/add.room.ts new file mode 100644 index 000000000..579f630d7 --- /dev/null +++ b/common/src/ws/message/room/add.room.ts @@ -0,0 +1,19 @@ +import type {DefaultWsInMessage, DefaultWsOutMessage} from "@common/ws/common"; +import {AddRoomBase} from "@common/model/ws.base"; + +export interface AddRoomWsInBody extends AddRoomBase { + channelUsers: number[]; + channelId: number; +} + +export interface AddRoomWsOutBody { + name: string | null; + p2p: boolean; + volume: number; + notifications: boolean; + users: number[]; + channelId: number | null; +} + +export type AddRoomWsInMessage = DefaultWsInMessage<"addRoom", "room", AddRoomWsInBody>; +export type AddRoomWsOutMessage = DefaultWsOutMessage<"addRoom", AddRoomWsOutBody>; diff --git a/common/src/ws/message/room/create.new.user.ts b/common/src/ws/message/room/create.new.user.ts new file mode 100644 index 000000000..13d94f3c7 --- /dev/null +++ b/common/src/ws/message/room/create.new.user.ts @@ -0,0 +1,11 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import {UserDto} from "@common/model/dto/user.dto"; + +export interface CreateNewUserWsInBody extends UserDto { + rooms: { + roomId: number; + users: number[]; + }[]; +} + +export type CreateNewUserWsInMessage = DefaultWsInMessage<"createNewUser", "room", CreateNewUserWsInBody>; diff --git a/common/src/ws/message/room/delete.channel.ts b/common/src/ws/message/room/delete.channel.ts new file mode 100644 index 000000000..0419f2bb3 --- /dev/null +++ b/common/src/ws/message/room/delete.channel.ts @@ -0,0 +1,9 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {ChannelDto} from "@common/model/dto/channel.dto"; + +export interface DeleteChannelWsInBody { + channelId: number; + roomIds: number[]; +} + +export type DeleteChannelWsInMessage = DefaultWsInMessage<"deleteChannel", "room", DeleteChannelWsInBody>; diff --git a/common/src/ws/message/room/delete.room.ts b/common/src/ws/message/room/delete.room.ts new file mode 100644 index 000000000..f27c316fb --- /dev/null +++ b/common/src/ws/message/room/delete.room.ts @@ -0,0 +1,7 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export interface DeleteRoomWsInBody { + roomId: number; +} + +export type DeleteRoomWsInMessage = DefaultWsInMessage<"deleteRoom", "room", DeleteRoomWsInBody>; diff --git a/common/src/ws/message/room/invite.user.ts b/common/src/ws/message/room/invite.user.ts new file mode 100644 index 000000000..b2dd3777e --- /dev/null +++ b/common/src/ws/message/room/invite.user.ts @@ -0,0 +1,11 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import { + NewRoom, + RoomExistedBefore +} from "@common/model/ws.base"; + +export interface InviteUserWsInBody extends NewRoom, RoomExistedBefore { + roomId: number; + users: number[]; +} +export type InviteUserWsInMessage = DefaultWsInMessage<"inviteUser", "room", InviteUserWsInBody>; diff --git a/common/src/ws/message/room/leave.user.ts b/common/src/ws/message/room/leave.user.ts new file mode 100644 index 000000000..e18c1dc73 --- /dev/null +++ b/common/src/ws/message/room/leave.user.ts @@ -0,0 +1,9 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export interface LeaveUserWsInBody { + roomId: number; + userId: number; + users: number[]; +} + +export type LeaveUserWsInMessage = DefaultWsInMessage<"leaveUser", "room", LeaveUserWsInBody>; diff --git a/common/src/ws/message/room/remove.online.user.ts b/common/src/ws/message/room/remove.online.user.ts new file mode 100644 index 000000000..c2be6f3e1 --- /dev/null +++ b/common/src/ws/message/room/remove.online.user.ts @@ -0,0 +1,6 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import {ChangeUserOnlineBase} from "@common/model/ws.base"; + +export type RemoveOnlineUserWsInBody = ChangeUserOnlineBase; + +export type RemoveOnlineUserWsInMessage = DefaultWsInMessage<"removeOnlineUser", "room", RemoveOnlineUserWsInBody>; diff --git a/common/src/ws/message/room/save.channel.settings.ts b/common/src/ws/message/room/save.channel.settings.ts new file mode 100644 index 000000000..eb87765f4 --- /dev/null +++ b/common/src/ws/message/room/save.channel.settings.ts @@ -0,0 +1,11 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {ChannelDto} from "@common/model/dto/channel.dto"; + +export interface SaveChannelSettingsWsInBody extends ChannelDto { + notifications: boolean; + volume: number; + p2p: boolean; + userId: number; +} + +export type SaveChannelSettingsWsInMessage = DefaultWsInMessage<"saveChannelSettings", "room", SaveChannelSettingsWsInBody>; diff --git a/common/src/ws/message/room/save.room.settings.ts b/common/src/ws/message/room/save.room.settings.ts new file mode 100644 index 000000000..e88ad4786 --- /dev/null +++ b/common/src/ws/message/room/save.room.settings.ts @@ -0,0 +1,5 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {RoomNoUsersDto} from "@common/model/dto/room.dto"; + +export type SaveRoomSettingsWsInBody = RoomNoUsersDto; +export type SaveRoomSettingsWsInMessage = DefaultWsInMessage<"saveRoomSettings", "room", SaveRoomSettingsWsInBody>; diff --git a/common/src/ws/message/room/show.i.type.ts b/common/src/ws/message/room/show.i.type.ts new file mode 100644 index 000000000..1909834b8 --- /dev/null +++ b/common/src/ws/message/room/show.i.type.ts @@ -0,0 +1,16 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage, +} from "@common/ws/common"; + +export interface ShowITypeWsInBody { + roomId: number; + userId: number; +} +export type ShowITypeWsInMessage = DefaultWsInMessage<"showIType", "room", ShowITypeWsInBody>; + +export interface ShowITypeWsOutBody { + roomId: number; +}; + +export type ShowITypeWsOutMessage = DefaultWsOutMessage<"showIType", ShowITypeWsOutBody>; diff --git a/common/src/ws/message/set.message.status.ts b/common/src/ws/message/set.message.status.ts new file mode 100644 index 000000000..795337dac --- /dev/null +++ b/common/src/ws/message/set.message.status.ts @@ -0,0 +1,21 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage, +} from "@common/ws/common"; +import type {MessageStatus} from "@common/model/enum/message.status"; + +export interface SetMessageStatusWsOutBody { + messagesIds: number[]; + status: MessageStatus; + roomId: number; +} + +export type SetMessageStatusWsOutMessage = DefaultWsOutMessage<"setMessageStatus", SetMessageStatusWsOutBody>; + +export interface SetMessageStatusWsInBody { + roomId: number; + status: MessageStatus; + messagesIds: number[]; +} + +export type SetMessageStatusWsInMessage = DefaultWsInMessage<"setMessageStatus", "ws-message",SetMessageStatusWsInBody>; diff --git a/common/src/ws/message/sync.history.ts b/common/src/ws/message/sync.history.ts new file mode 100644 index 000000000..5211a54af --- /dev/null +++ b/common/src/ws/message/sync.history.ts @@ -0,0 +1,22 @@ +import type { + DefaultWsOutMessage, + ResponseWsInMessage, +} from "@common/ws/common"; +import {MessageModelDto} from "@common/model/dto/message.model.dto"; + +export interface SyncHistoryWsOutBody { + roomIds: number[]; + messagesIds: number[]; + onServerMessageIds: number[]; + receivedMessageIds: number[]; + lastSynced: number; +} +export type SyncHistoryWsOutMessage = DefaultWsOutMessage<"syncHistory", SyncHistoryWsOutBody>; + +export interface SyncHistoryWsInBody { + readMessageIds: number[]; + receivedMessageIds: number[]; + messages: MessageModelDto[]; +}; + +export type SyncHistoryWsInMessage = ResponseWsInMessage diff --git a/common/src/ws/message/webrtc-transfer/accept.call.ts b/common/src/ws/message/webrtc-transfer/accept.call.ts new file mode 100644 index 000000000..f2b68f6db --- /dev/null +++ b/common/src/ws/message/webrtc-transfer/accept.call.ts @@ -0,0 +1,10 @@ +import {DefaultWsInMessage} from "@common/ws/common"; +import { + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; + +export interface AcceptCallWsInBody extends OpponentWsId, WebRtcDefaultMessage { + +} +export type AcceptCallWsInMessage = DefaultWsInMessage<"acceptCall", "webrtcTransfer:*", AcceptCallWsInBody>; diff --git a/common/src/ws/message/webrtc-transfer/accept.file.ts b/common/src/ws/message/webrtc-transfer/accept.file.ts new file mode 100644 index 000000000..705107d5e --- /dev/null +++ b/common/src/ws/message/webrtc-transfer/accept.file.ts @@ -0,0 +1,16 @@ +import { + DefaultWsInMessage, + DefaultWsOutMessage +} from "@common/ws/common"; +import {WebRtcDefaultMessage} from "@common/model/webrtc.base"; + + +export interface AcceptFileWsInBody extends WebRtcDefaultMessage { + received: number; +} + +export type AcceptFileWsOutBody = AcceptFileWsInBody; + +export type AcceptFileWsInMessage = DefaultWsInMessage<"acceptFile", "peerConnection:*", AcceptFileWsInBody>; + +export type AcceptFileWsOutMessage = DefaultWsOutMessage<"acceptFile", AcceptFileWsOutBody> diff --git a/common/src/ws/message/webrtc-transfer/reply.call.ts b/common/src/ws/message/webrtc-transfer/reply.call.ts new file mode 100644 index 000000000..b28a07817 --- /dev/null +++ b/common/src/ws/message/webrtc-transfer/reply.call.ts @@ -0,0 +1,6 @@ +import {DefaultWsInMessage} from "@common/ws/common"; +import {ReplyWebRtc} from "@common/model/webrtc.base"; + +export type ReplyCallWsInBody = ReplyWebRtc; + +export type ReplyCallWsInMessage = DefaultWsInMessage<"replyCall", "webrtcTransfer:*", ReplyCallWsInBody>; diff --git a/common/src/ws/message/webrtc-transfer/reply.file.ts b/common/src/ws/message/webrtc-transfer/reply.file.ts new file mode 100644 index 000000000..675b6cc87 --- /dev/null +++ b/common/src/ws/message/webrtc-transfer/reply.file.ts @@ -0,0 +1,6 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {ReplyWebRtc} from "@common/model/webrtc.base"; + +export type ReplyFileWsInBody = ReplyWebRtc; + +export type ReplyFileWsInMessage = DefaultWsInMessage<"replyFile", "webrtcTransfer:*", ReplyFileWsInBody>; diff --git a/common/src/ws/message/webrtc/notify.call.active.ts b/common/src/ws/message/webrtc/notify.call.active.ts new file mode 100644 index 000000000..e4e5af81b --- /dev/null +++ b/common/src/ws/message/webrtc/notify.call.active.ts @@ -0,0 +1,12 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import { + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; + +export interface NotifyCallActiveWsInBody extends WebRtcDefaultMessage, OpponentWsId { + roomId: number; + userId: number; +} + +export type NotifyCallActiveWsInMessage = DefaultWsInMessage<"notifyCallActive", "webrtc", NotifyCallActiveWsInBody>; diff --git a/common/src/ws/message/webrtc/offer.call.ts b/common/src/ws/message/webrtc/offer.call.ts new file mode 100644 index 000000000..9c1fa618d --- /dev/null +++ b/common/src/ws/message/webrtc/offer.call.ts @@ -0,0 +1,25 @@ +import type { + DefaultWsInMessage, + RequestWsOutMessage, + ResponseWsInMessage, +} from "@common/ws/common"; +import type { + BrowserBase, + OpponentWsId, + WebRtcDefaultMessage, + WebRtcSetConnectionIdBody, +} from "@common/model/webrtc.base"; + + +export interface OfferCallRequestWsOutBody extends BrowserBase { + roomId: number; +} + +export interface OfferCallWsInBody extends OpponentWsId, WebRtcDefaultMessage, OfferCallRequestWsOutBody { + userId: number; + time: number; +} + +export type OfferCallWsInMessage = DefaultWsInMessage<"offerCall", "webrtc", OfferCallWsInBody>; +export type OfferCallRequestWsOutMessage = RequestWsOutMessage<"offerCall", OfferCallRequestWsOutBody>; +export type OfferCallResponseWsInMessage = ResponseWsInMessage; diff --git a/common/src/ws/message/webrtc/offer.file.ts b/common/src/ws/message/webrtc/offer.file.ts new file mode 100644 index 000000000..2defe5b91 --- /dev/null +++ b/common/src/ws/message/webrtc/offer.file.ts @@ -0,0 +1,33 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage, + MultiResponseMessage, + ResponseWsInMessage +} from "@common/ws/common"; +import { + BrowserBase, + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; +import {RequestWsOutMessage} from "@common/ws/common"; +import {WebRtcSetConnectionIdMessage} from "@common/ws/message/sync/set.connection.id"; + + +export type OfferFileRequestWsOutBody = { + roomId: number; + threadId:number | null; + browser: string; + name: string; + size: number; +} + +export type OfferFileRequest = RequestWsOutMessage<"offerFile", OfferFileRequestWsOutBody> + +export type OfferFileResponse = WebRtcSetConnectionIdMessage; + +export interface OfferFileBody extends OfferFileRequestWsOutBody, OpponentWsId, WebRtcDefaultMessage { + userId: number; + time: number; +} + +export type OfferFileResponse = MultiResponseMessage<"offerFile", "webrtc", OfferFileBody>; diff --git a/common/src/ws/message/webrtc/offer.message.ts b/common/src/ws/message/webrtc/offer.message.ts new file mode 100644 index 000000000..f678cc2f9 --- /dev/null +++ b/common/src/ws/message/webrtc/offer.message.ts @@ -0,0 +1,16 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import { + BrowserBase, + OpponentWsId, + WebRtcDefaultMessage +} from "@common/model/webrtc.base"; + + +export interface OfferMessageWsInBody extends OpponentWsId, WebRtcDefaultMessage { + content: BrowserBase; + roomId: number; + userId: number; + time: number; +} + +export type OfferMessageWsInMessage = DefaultWsInMessage<"offerMessage", "webrtc", OfferMessageWsInBody>; diff --git a/common/src/ws/message/ws-message/delete.message.ts b/common/src/ws/message/ws-message/delete.message.ts new file mode 100644 index 000000000..8e46f4784 --- /dev/null +++ b/common/src/ws/message/ws-message/delete.message.ts @@ -0,0 +1,10 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import {UserDto} from "@common/model/dto/user.dto"; + +export interface DeleteMessageWsInBody { + roomId: number; + id: number; + edited: number; +} + +export type DeleteMessageWsInMessage = DefaultWsInMessage<"deleteMessage", "ws-message", DeleteMessageWsInBody>; diff --git a/common/src/ws/message/ws-message/print.message.ts b/common/src/ws/message/ws-message/print.message.ts new file mode 100644 index 000000000..1d2d1aa2f --- /dev/null +++ b/common/src/ws/message/ws-message/print.message.ts @@ -0,0 +1,21 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage, +} from "@common/ws/common"; +import type {MessageModelDto} from "@common/model/dto/message.model.dto"; +import type {GiphyDto} from "@common/model/dto/giphy.dto"; + +export type PrintMessageWsInMessage = DefaultWsInMessage<"printMessage", "ws-message", MessageModelDto>; + +export interface PrintMessageWsOutBody { + content: string; + roomId: number; + files: number[]; + id: number; + timeDiff: number; + parentMessage: number | null; + tags: Record; + giphies: GiphyDto[]; +} + +export type PrintMessageWsOutMessage = DefaultWsOutMessage<"printMessage", PrintMessageWsOutBody>; diff --git a/common/src/ws/message/ws/ping.ts b/common/src/ws/message/ws/ping.ts new file mode 100644 index 000000000..927025b5d --- /dev/null +++ b/common/src/ws/message/ws/ping.ts @@ -0,0 +1,6 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export interface PingWsInBody { + time: string; +} +export type PingWsInMessage = DefaultWsInMessage<"ping", "ws", PingWsInBody>; diff --git a/common/src/ws/message/ws/pong.ts b/common/src/ws/message/ws/pong.ts new file mode 100644 index 000000000..ca0968162 --- /dev/null +++ b/common/src/ws/message/ws/pong.ts @@ -0,0 +1,11 @@ +import type { + DefaultWsInMessage, + DefaultWsOutMessage +} from "@common/ws/common"; + +export interface PongBody { + time: string; +} +export type PongWsOutMessage = DefaultWsOutMessage<"pong", PongBody>; + +export type PongWsInMessage = DefaultWsInMessage<"pong", "ws", PongBody> diff --git a/common/src/ws/message/ws/set.profile.image.ts b/common/src/ws/message/ws/set.profile.image.ts new file mode 100644 index 000000000..b73108f9c --- /dev/null +++ b/common/src/ws/message/ws/set.profile.image.ts @@ -0,0 +1,6 @@ +import {DefaultWsInMessage} from "@common/ws/common"; + +export interface SetProfileImageWsInBody { + url: string; +} +export type SetProfileImageWsInMessage = DefaultWsInMessage<"setProfileImage", "ws", SetProfileImageWsInBody>; diff --git a/common/src/ws/message/ws/set.settings.ts b/common/src/ws/message/ws/set.settings.ts new file mode 100644 index 000000000..5507c2341 --- /dev/null +++ b/common/src/ws/message/ws/set.settings.ts @@ -0,0 +1,11 @@ +import type { + DefaultWsInMessage, + RequestWsOutMessage, + ResponseWsInMessage +} from "@common/ws/common"; +import type {UserSettingsDto} from "@common/model/dto/user.settings.dto"; +import {MultiResponseMessage} from "@common/ws/common"; + +export type SetSettingBody = UserSettingsDto; +export type SetSettingsMessage = MultiResponseMessage<"setSettings", "ws", SetSettingBody>; +export type SetSettingsWsOutMessage = RequestWsOutMessage<"setSettings", SetSettingBody> diff --git a/common/src/ws/message/ws/set.user.profile.ts b/common/src/ws/message/ws/set.user.profile.ts new file mode 100644 index 000000000..ec0003274 --- /dev/null +++ b/common/src/ws/message/ws/set.user.profile.ts @@ -0,0 +1,11 @@ +import type { + DefaultWsInMessage, + MultiResponseMessage, + RequestWsOutMessage +} from "@common/ws/common"; +import type {UserProfileDtoWoImage} from "@common/model/dto/user.profile.dto"; + +export type SetUserProfileBody = UserProfileDtoWoImage; +export type SetUserProfileMessage = MultiResponseMessage<"setUserProfile", "ws", SetUserProfileBody>; + +export type SetUserProfileWsOutMessage = RequestWsOutMessage<"setUserProfile", SetUserProfileBody>; diff --git a/common/src/ws/message/ws/set.ws.id.ts b/common/src/ws/message/ws/set.ws.id.ts new file mode 100644 index 000000000..f15b83765 --- /dev/null +++ b/common/src/ws/message/ws/set.ws.id.ts @@ -0,0 +1,18 @@ +import {DefaultWsInMessage} from "@common/ws/common"; +import {RoomDto} from "@common/model/dto/room.dto"; +import {ChannelDto} from "@common/model/dto/channel.dto"; +import {UserDto} from "@common/model/dto/user.dto"; +import {UserProfileDto} from "@common/model/dto/user.profile.dto"; +import {UserSettingsDto} from "@common/model/dto/user.settings.dto"; + +export interface SetWsIdBody { + rooms: RoomDto[]; + opponentWsId: string; + channels: ChannelDto[]; + users: UserDto[]; + online: Record; + time: number; + profile: UserProfileDto; + settings: UserSettingsDto; +} +export type SetWsIdWsInMessage = DefaultWsInMessage<"setWsId", "ws", SetWsIdBody>; diff --git a/common/src/ws/message/ws/user.profile.changed.ts b/common/src/ws/message/ws/user.profile.changed.ts new file mode 100644 index 000000000..2d1ae201f --- /dev/null +++ b/common/src/ws/message/ws/user.profile.changed.ts @@ -0,0 +1,5 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; +import type {UserDto} from "@common/model/dto/user.dto"; + +export type UserProfileChangedWsInBody = UserDto; +export type UserProfileChangedWsInMessage = DefaultWsInMessage<"userProfileChanged", "ws", UserProfileChangedWsInBody>; diff --git a/common/tsconfig.json b/common/tsconfig.json new file mode 100644 index 000000000..7fd00cbc4 --- /dev/null +++ b/common/tsconfig.json @@ -0,0 +1,29 @@ +{ + "exclude": [ + "node_modules", + "dist", + ], + "compilerOptions": { + "paths": { + "@common/*": ["./src/*"] + }, + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/common/yarn.lock b/common/yarn.lock new file mode 100644 index 000000000..91da40833 --- /dev/null +++ b/common/yarn.lock @@ -0,0 +1,1477 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint/eslintrc@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae" + integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.1" + globals "^13.9.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@typescript-eslint/eslint-plugin@^5.17.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.21.0.tgz#bfc22e0191e6404ab1192973b3b4ea0461c1e878" + integrity sha512-fTU85q8v5ZLpoZEyn/u1S2qrFOhi33Edo2CZ0+q1gDaWWm0JuPh3bgOyU8lM0edIEYgKLDkPFiZX2MOupgjlyg== + dependencies: + "@typescript-eslint/scope-manager" "5.21.0" + "@typescript-eslint/type-utils" "5.21.0" + "@typescript-eslint/utils" "5.21.0" + debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.17.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.21.0.tgz#6cb72673dbf3e1905b9c432175a3c86cdaf2071f" + integrity sha512-8RUwTO77hstXUr3pZoWZbRQUxXcSXafZ8/5gpnQCfXvgmP9gpNlRGlWzvfbEQ14TLjmtU8eGnONkff8U2ui2Eg== + dependencies: + "@typescript-eslint/scope-manager" "5.21.0" + "@typescript-eslint/types" "5.21.0" + "@typescript-eslint/typescript-estree" "5.21.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.21.0.tgz#a4b7ed1618f09f95e3d17d1c0ff7a341dac7862e" + integrity sha512-XTX0g0IhvzcH/e3393SvjRCfYQxgxtYzL3UREteUneo72EFlt7UNoiYnikUtmGVobTbhUDByhJ4xRBNe+34kOQ== + dependencies: + "@typescript-eslint/types" "5.21.0" + "@typescript-eslint/visitor-keys" "5.21.0" + +"@typescript-eslint/type-utils@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.21.0.tgz#ff89668786ad596d904c21b215e5285da1b6262e" + integrity sha512-MxmLZj0tkGlkcZCSE17ORaHl8Th3JQwBzyXL/uvC6sNmu128LsgjTX0NIzy+wdH2J7Pd02GN8FaoudJntFvSOw== + dependencies: + "@typescript-eslint/utils" "5.21.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.21.0.tgz#8cdb9253c0dfce3f2ab655b9d36c03f72e684017" + integrity sha512-XnOOo5Wc2cBlq8Lh5WNvAgHzpjnEzxn4CJBwGkcau7b/tZ556qrWXQz4DJyChYg8JZAD06kczrdgFPpEQZfDsA== + +"@typescript-eslint/typescript-estree@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.21.0.tgz#9f0c233e28be2540eaed3df050f0d54fb5aa52de" + integrity sha512-Y8Y2T2FNvm08qlcoSMoNchh9y2Uj3QmjtwNMdRQkcFG7Muz//wfJBGBxh8R7HAGQFpgYpdHqUpEoPQk+q9Kjfg== + dependencies: + "@typescript-eslint/types" "5.21.0" + "@typescript-eslint/visitor-keys" "5.21.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.21.0", "@typescript-eslint/utils@^5.10.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.21.0.tgz#51d7886a6f0575e23706e5548c7e87bce42d7c18" + integrity sha512-q/emogbND9wry7zxy7VYri+7ydawo2HDZhRZ5k6yggIvXa7PvBbAAZ4PFH/oZLem72ezC4Pr63rJvDK/sTlL8Q== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.21.0" + "@typescript-eslint/types" "5.21.0" + "@typescript-eslint/typescript-estree" "5.21.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.21.0.tgz#453fb3662409abaf2f8b1f65d515699c888dd8ae" + integrity sha512-SX8jNN+iHqAF0riZQMkm7e8+POXa/fXw5cxL+gjpyP+FI+JVNhii53EmQgDAfDcBpFekYSlO0fGytMQwRiMQCA== + dependencies: + "@typescript-eslint/types" "5.21.0" + eslint-visitor-keys "^3.0.0" + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.7.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-properties@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es-abstract@^1.19.1, es-abstract@^1.19.2: + version "1.19.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.5.tgz#a2cb01eb87f724e815b278b0dd0d00f36ca9a7f1" + integrity sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-find-rules@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-find-rules/-/eslint-find-rules-4.1.0.tgz#f32671050cafbbf140592ab7fd04c2acc9117f5c" + integrity sha512-2nLJ0UrQRUGbOX8ksSxTgDNWGBW0RidIEIMlwNe1YR4RkjYksFDPYWf3WO9QFWM2NqkuHlTTeyOM94vaNwaFVw== + dependencies: + cliui "^3.2.0" + eslint-rule-documentation "^1.0.23" + glob "^7.2.0" + which "^1.3.1" + window-size "^0.3.0" + yargs "^16.2.0" + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-babel@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560" + integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-plugin-import@^2.25.4: + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" + has "^1.0.3" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-jest@^26.1.4: + version "26.1.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.1.5.tgz#6cfca264818d6d6aa120b019dab4d62b6aa8e775" + integrity sha512-su89aDuljL9bTjEufTXmKUMSFe2kZUL9bi7+woq+C2ukHZordhtfPm4Vg+tdioHBaKf8v3/FXW9uV0ksqhYGFw== + dependencies: + "@typescript-eslint/utils" "^5.10.0" + +eslint-plugin-only-warn@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.3.tgz#a75f3a9ded7f03e9808e75ec27f22b644084506e" + integrity sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw== + +eslint-plugin-sonarjs@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.13.0.tgz#c34d140cc90abaaed38f5a5201a2ccdebe398862" + integrity sha512-t3m7ta0EspzDxSOZh3cEOJIJVZgN/TlJYaBGnQlK6W/PZNbWep8q4RQskkJkA7/zwNpX0BaoEOSUUrqaADVoqA== + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + +eslint-rule-documentation@^1.0.23: + version "1.0.23" + resolved "https://registry.yarnpkg.com/eslint-rule-documentation/-/eslint-rule-documentation-1.0.23.tgz#4e0886145597a78d24524ec7e0cf18c6fedc23a8" + integrity sha512-pWReu3fkohwyvztx/oQWWgld2iad25TfUdi6wvhhaDPIQjHU/pyvlKgXFw1kX31SQK2Nq9MH+vRDWB0ZLy8fYw== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.12.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239" + integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw== + dependencies: + "@eslint/eslintrc" "^1.2.2" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.3.0" + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.13.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b" + integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +ignore@^5.1.8, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-logger@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/lines-logger/-/lines-logger-2.1.2.tgz#85b619ee849938323615e40fa8cee76d24532de7" + integrity sha512-pSn6qr2K0tf28TgrVMMQxinADrZP3xObxxUBlhTNWWRZToRtrjHihzW7UOKlOypKDDiP3uk/DRjd6dqQsg1coA== + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimatch@^3.0.4, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.20.0, resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typescript@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== + +unbox-primitive@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +window-size@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.3.0.tgz#b8f0b66e325d22160751e496337e44b45b727546" + integrity sha1-uPC2bjJdIhYHUeSWM35EtFtydUY= + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" diff --git a/docker/nginx.conf b/docker/nginx.conf index 7c95f9b39..9ef7a44e0 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -36,7 +36,6 @@ http { ssl_certificate /etc/nginx/ssl/certificate.crt; ssl_certificate_key /etc/nginx/ssl/server.key; - underscores_in_headers on; location ~ "^/photo/(?\w{8}_(?.*))$" { add_header Content-Disposition 'inline; filename="$filename"'; diff --git a/docker/pychat.org/turnserver.conf b/docker/pychat.org/turnserver.conf index d1514547f..bc5b6afb2 100644 --- a/docker/pychat.org/turnserver.conf +++ b/docker/pychat.org/turnserver.conf @@ -1,3 +1,3 @@ user=pychat:pypass realm=pychat.org -server-name=pychat.org \ No newline at end of file +server-name=pychat.org diff --git a/download_content.sh b/download_content.sh index 781c685e6..2b2124278 100755 --- a/download_content.sh +++ b/download_content.sh @@ -75,6 +75,85 @@ update_docker() { safeRunCommand docker push deathangel908/pychat-test } +minikube_reload_frontend() { + minikube_reload backend DockerfileBackend +} + +minikube_reload_backend() { + minikube_reload frontend DockerfileFrontend +} + +minikube_reload() { + image=$1 + dockerfile=$2 + # service can be not deployed yet, do not wait + kubectl delete -f ./kubernetes/$image.yaml --wait=true + safeRunCommand docker build -f ./kubernetes/$dockerfile -t deathangel908/pychat-$image . + safeRunCommand minikube image rm deathangel908/pychat-$image + safeRunCommand minikube image load deathangel908/pychat-$image +} + + +minikube_frontend() { + safeRunCommand minikube_reload_frontend + safeRunCommand kubectl apply -f kubernetes/frontend.yaml +} + +minikube_backend() { + safeRunCommand minikube_reload_backend + safeRunCommand kubectl apply -f kubernetes/backend.yaml +} + +minikube_delete_all() { + kubectl delete -f kubernetes/ingress.yaml --wait=true + kubectl delete -f kubernetes/backend.yaml --wait=true + kubectl delete -f kubernetes/migrate-backend.yaml --wait=true + kubectl delete -f kubernetes/mariadb.yaml --wait=true + kubectl delete -f kubernetes/redis.yaml --wait=true + kubectl delete -f kubernetes/secret.yaml --wait=true + kubectl delete -f kubernetes/config-map.yaml --wait=true + kubectl delete -f kubernetes/pv-pychat-photo.yaml --wait=true + kubectl delete -f kubernetes/pv-pychat-redis.yaml --wait=true + kubectl delete -f kubernetes/pv-pychat-mariadb.yaml --wait=true + kubectl delete -f kubernetes/namespace.yaml --wait=true +} + +minikube_nginx() { + helm repo add stable https://charts.helm.sh/stable + helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx + helm install nginx-ingress ingress-nginx/ingress-nginx +} +minikube_certificate() { + # Profile → API Tokens → Create Token. https://dash.cloudflare.com/profile/api-tokens Permissions: Zone — DNS — Edit, Zone — Zone — Read; Zone Resources: Include — All Zones + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.yaml + helm repo add jetstack https://charts.jetstack.io + helm repo update + helm template cert-manager jetstack/cert-manager --namespace cert-manager --version v1.8.0| kubectl apply -f - + kubectl apply -f kubernetes/cf-secret.yaml + kubectl apply -f kubernetes/cert-manager.yaml + # helm --namespace cert-manager delete cert-manager + # kubectl delete namespace cert-manager + # kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.8.0/cert-manager.crds.yaml + # kubectl delete apiservice v1beta1.webhook.cert-manager.io +} + +minikube_all() { + safeRunCommand kubectl apply -f kubernetes/namespace.yaml + safeRunCommand kubectl apply -f kubernetes/pv-pychat-photo.yaml + safeRunCommand kubectl apply -f kubernetes/pv-pychat-redis.yaml + safeRunCommand kubectl apply -f kubernetes/pv-pychat-mariadb.yaml + safeRunCommand kubectl apply -f kubernetes/config-map.yaml + safeRunCommand kubectl apply -f kubernetes/backend-secret.yaml + safeRunCommand kubectl apply -f kubernetes/mariadb.yaml + safeRunCommand kubectl apply -f kubernetes/redis.yaml + #safeRunCommand minikube_reload_backend + safeRunCommand kubectl apply -f kubernetes/migrate-backend.yaml + safeRunCommand kubectl apply -f kubernetes/backend.yaml + #safeRunCommand minikube_reload_frontend + safeRunCommand kubectl apply -f kubernetes/frontend.yaml + safeRunCommand kubectl apply -f kubernetes/ingress.yaml +} + rename_domain() { exist_domain="pychat\.org" your_domain="$1" @@ -440,8 +519,11 @@ generate_secret_key() { fi echo "" >> $BE_DIRECTORY/chat/settings.py echo -n "SECRET_KEY = '" >> $BE_DIRECTORY/chat/settings.py - LC_ALL=C LC_CTYPE=C tr -dc 'A-Za-z0-9!@#$%^&*(\-\_\=\+)' > $BE_DIRECTORY/chat/settings.py - echo "'" >> $BE_DIRECTORY/chat/settings.py + LC_ALL=C + LC_CTYPE=C + SECRET_KEY=$(tr -dc 'A-Za-z0-9!@#$%^&*(\-\_\=\+)' > $BE_DIRECTORY/chat/settings.py } if [ "$1" = "post_fontello_conf" ]; then @@ -492,6 +574,12 @@ elif [ "$1" = "copy_docker_prod_files" ]; then copy_docker_prod_files elif [ "$1" = "copy_root_fs" ]; then copy_root_fs +elif [ "$1" = "minikube_all" ]; then + minikube_all +elif [ "$1" = "minikube_frontend" ]; then + minikube_frontend +elif [ "$1" = "minikube_backend" ]; then + minikube_backend elif [ "$1" = "redirect" ]; then safeRunCommand sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080 safeRunCommand sudo iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-ports 8080 @@ -538,6 +626,9 @@ else chp copy_root_fs "Creates soft links from \e[96m$PROJECT_ROOT/rootfs\e[0;33;40m to \e[96m/\e[0;33;40m" chp build_nginx "Build nginx with http-upload-module and installs it; . Usage: \e[92msh download_content.sh build_nginx 1.15.3 2.3.0 /tmp/depsFileList.txt\e[0;33;40m where 1.15 is nginx version, 2.3.0 is upload-http-module version" chp create_django_tables "Creates database tables and data" + chp minikube_all "Creates/updates kubernetes cluster" + chp minikube_backend "Build backend docker file and deploy/update it in minikube" + chp minikube_frontend "Build frontend docker file and deploy/update it in minikube" exit 1 fi diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index afe6d7943..9920d157f 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -23,6 +23,7 @@ "rules": { "no-warning-comments": 0, "object-curly-spacing": 0, + "sort-keys": 0, "init-declarations": 0, // poor choice "func-style": [1, "declaration"], "one-var": 0, // poor choice @@ -56,6 +57,7 @@ "@typescript-eslint/no-non-null-assertion": 0, "@typescript-eslint/no-unused-vars-experimental": 0, // doesn't work with vue "@typescript-eslint/no-type-alias": 0, + "@typescript-eslint/no-parameter-properties": 0, "@typescript-eslint/no-magic-numbers": [ "error", { @@ -93,7 +95,7 @@ "import/no-relative-parent-imports": 1, "import/dynamic-import-chunkname": 0, "import/exports-last":1, - "import/extensions": [1, "never", {"json": "always", "vue": "always", "sass": "always", "png": "always", "wav": "always", "mp3": "always"}], +// "import/extensions": [1, "never", {"json": "always", "vue": "always", "sass": "always", "png": "always", "wav": "always", "mp3": "always"}], "import/first":1, "import/group-exports": 0, // vue3 "import/max-dependencies": 0, diff --git a/frontend/.nvmrc b/frontend/.nvmrc index 898c8715b..04c9e2bf6 100644 --- a/frontend/.nvmrc +++ b/frontend/.nvmrc @@ -1 +1 @@ -14.17 +v16.15 diff --git a/frontend/build/development.json b/frontend/build/development.json index 95fdee33e..2d604106a 100644 --- a/frontend/build/development.json +++ b/frontend/build/development.json @@ -3,9 +3,9 @@ "IS_DEBUG": true, "ELECTRON_IGNORE_SSL": true, "PUBLIC_PATH": null, - "GOOGLE_OAUTH_2_CLIENT_ID": false, - "FACEBOOK_APP_ID": false, - "RECAPTCHA_PUBLIC_KEY": false, + "GOOGLE_OAUTH_2_CLIENT_ID": "620421656154-8boctt2h7h5meskhe7vh8aiargj0nae9.apps.googleusercontent.com", + "FACEBOOK_APP_ID": "894051850700207", + "RECAPTCHA_PUBLIC_KEY": "6LchGhwTAAAAAJwCRgfNKraWrFtCdKPFo0Ri8DLt", "AUTO_REGISTRATION": false, "ISSUES": false, "GIPHY_API_KEY": "thZMTtWsaBQAPDIAY461GzYTctuYIeIj", diff --git a/frontend/build/static-server.js b/frontend/build/static-server.js index de2886baf..9f0ab7148 100644 --- a/frontend/build/static-server.js +++ b/frontend/build/static-server.js @@ -1,9 +1,8 @@ const servor = require('servor'); -const {promisify} = require('util'); -const {readFile} = require('fs'); +const {readFile} = require('fs/promises'); async function readFileAsync(name) { - return promisify(readFile)(name); + return readFile(name); } async function main() { diff --git a/frontend/build/utils.ts b/frontend/build/utils.ts index 400523285..ba574492b 100644 --- a/frontend/build/utils.ts +++ b/frontend/build/utils.ts @@ -14,6 +14,9 @@ function makeid(length) { } export function getSmileyUrls() { + // TODO + // https://www.youtube.com/watch?v=ff4fgQxPaO0&list=PLNYkxOF6rcIDA1uGhqy45bqlul0VcvKMr&index=34&t=328s&pp=iAQB&ab_channel=GoogleChromeDevelopers + // Either remove json and store images as smth else, either at least move json to a string, since JSON.parse is after than object literal const assets = []; Object.values(smileysData).forEach(tab => { Object.values(tab).forEach((v: any) => { diff --git a/frontend/build/vite.config.ts b/frontend/build/vite.config.ts index 031160561..805fc36c0 100644 --- a/frontend/build/vite.config.ts +++ b/frontend/build/vite.config.ts @@ -15,6 +15,7 @@ import viteChecker from 'vite-plugin-checker' import {viteStaticCopy} from 'vite-plugin-static-copy' import viteCompression from 'vite-plugin-compression' import viteVisualizer from 'rollup-plugin-visualizer' +import Inspector from "vite-plugin-vue-inspector"; export default defineConfig(async({command, mode}) => { let key, cert, ca, gitHash; @@ -31,14 +32,33 @@ export default defineConfig(async({command, mode}) => { const PYCHAT_CONSTS = getConsts(gitHash, command); const srcDir = resolve(__dirname, '..', 'src'); + const commonDir = resolve(__dirname, '..', '..', 'common', 'src'); const distDir = resolve(__dirname, '..', 'dist'); const nodeModulesDir = resolve(__dirname, '..', 'node_modules'); const swFilePath = resolve(srcDir, 'ts', 'sw.ts'); const smileyPath = resolve(srcDir, 'assets', 'smileys.json'); + const plugins = [] + if (!process.env.VITE_LIGHT) { + plugins.push(viteChecker({ + typescript: true, + vueTsc: true, + ...(process.env.VITE_LINT ? { + eslint: { + lintCommand: 'eslint --ext .vue --ext .ts ts vue', + dev: {} + } + } : null) + } + ),) + plugins.push(viteCompression({ + filter: () => true, + })) + } return { resolve: { alias: [ {find: '@', replacement: srcDir}, + {find: '@common', replacement: commonDir}, ], }, ...(PYCHAT_CONSTS.PUBLIC_PATH ? {base: PYCHAT_CONSTS.PUBLIC_PATH} : null), @@ -48,6 +68,9 @@ export default defineConfig(async({command, mode}) => { }, plugins: [ vue(), + Inspector({ + vue: 3, + }), ...(!process.env.VITE_LIGHT ? [ viteChecker({ typescript: !process.env.VITE_LIGHT, diff --git a/frontend/package.json b/frontend/package.json index ff8717769..830acb790 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Opensource chat", "scripts": { - "start": "vite --config build/vite.config.ts", + "start": "vite --config build/vite.config.ts --force", "build": "VITE_LIGHT=true vite --config build/vite.config.ts build", "build:light": "VITE_LIGHT=true vite --config build/vite.config.ts build", "start:lint": "VITE_LINT=true vite --config build/vite.config.ts", @@ -24,6 +24,9 @@ "bugs": { "url": "https://github.com/akoidan/pychat/issues" }, + "engines": { + "node": ">=16.0.0 <17" + }, "homepage": "https://github.com/akoidan/pychat#readme", "devDependencies": { "@babel/types": "^7.17.0", @@ -57,6 +60,7 @@ "vite-plugin-checker": "^0.4.4", "vite-plugin-compression": "^0.5.1", "vite-plugin-static-copy": "https://github.com/akoidan/vite-plugin-static-copy", + "vite-plugin-vue-inspector": "^0.3.0", "vue-tsc": "^0.33.9" }, "dependencies": { diff --git a/frontend/src/ts/classes/DatabaseWrapper.ts b/frontend/src/ts/classes/DatabaseWrapper.ts index 69257c950..a9a937b1f 100644 --- a/frontend/src/ts/classes/DatabaseWrapper.ts +++ b/frontend/src/ts/classes/DatabaseWrapper.ts @@ -1,33 +1,22 @@ -import type { - IStorage, - SetFileIdsForMessage, - SetRoomsUsers, -} from "@/ts/types/types"; +import type {ImageType} from "@common/model/enum/image.type"; +import type {IStorage, SetFileIdsForMessage, SetRoomsUsers} from "@/ts/types/types"; import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; import type { - BlobType, ChannelModel, ChannelsDictModel, CurrentUserInfoModel, CurrentUserSettingsModel, FileModel, MessageModel, - MessageStatus, + MessageStatusModel, RoomDictModel, RoomModel, RoomSettingsModel, UserModel, } from "@/ts/types/model"; -import { - convertNumberToSex, - convertSexToNumber, - convertSexToString, - convertStringSexToNumber, - convertToBoolean, - getChannelDict, - getRoomsBaseDict, -} from "@/ts/types/converters"; +import {MessageStatusInner} from "@/ts/types/model"; +import {convertToBoolean, getChannelDict, getRoomsBaseDict} from "@/ts/types/converters"; import type { ChannelDB, FileDB, @@ -42,6 +31,7 @@ import type { import type {SetStateFromStorage} from "@/ts/types/dto"; import type {MainWindow} from "@/ts/classes/MainWindow"; + type TransactionCb = (t: SQLTransaction, ...rest: unknown[]) => void; type QueryObject = [string, any[]]; @@ -61,7 +51,7 @@ export default class DatabaseWrapper implements IStorage { if (!window.openDatabase) { throw Error("DatabaseWrapper not supported"); } - this.db = window.openDatabase("v157", "", "Messages database", 10 * 1024 * 1024); + this.db = window.openDatabase("v163", "", "Messages database", 10 * 1024 * 1024); this.logger = loggerFactory.getLoggerColor("db", "#753e01"); } @@ -71,14 +61,14 @@ export default class DatabaseWrapper implements IStorage { let t: SQLTransaction = await new Promise((resolve, reject) => { this.db.changeVersion(this.db.version, "1.0", resolve, reject); }); - t = await this.runSql(t, "CREATE TABLE user (id integer primary key, user text, sex integer NOT NULL CHECK (sex IN (0,1,2)), deleted boolean NOT NULL CHECK (deleted IN (0,1)), country_code text, country text, region text, city text, thumbnail text, last_time_online integer)"); - t = await this.runSql(t, "CREATE TABLE channel (id integer primary key, name text, deleted boolean NOT NULL CHECK (deleted IN (0,1)), creator INTEGER REFERENCES user(id))"); - t = await this.runSql(t, "CREATE TABLE room (id integer primary key, name text, p2p boolean NOT NULL CHECK (p2p IN (0,1)), notifications boolean NOT NULL CHECK (notifications IN (0,1)), volume integer, deleted boolean NOT NULL CHECK (deleted IN (0,1)), channel_id INTEGER REFERENCES channel(id), is_main_in_channel boolean NOT NULL CHECK (is_main_in_channel IN (0,1)), creator INTEGER REFERENCES user(id))"); + t = await this.runSql(t, "CREATE TABLE user (id integer primary key, username text, sex text NOT NULL CHECK (sex IN ('MALE', 'FEMALE','OTHER')), deleted boolean NOT NULL CHECK (deleted IN (0,1)), country_code text, country text, region text, city text, thumbnail text, last_time_online integer)"); + t = await this.runSql(t, "CREATE TABLE channel (id integer primary key, name text, deleted boolean NOT NULL CHECK (deleted IN (0,1)), creator_id INTEGER REFERENCES user(id))"); + t = await this.runSql(t, "CREATE TABLE room (id integer primary key, name text, p2p boolean NOT NULL CHECK (p2p IN (0,1)), notifications boolean NOT NULL CHECK (notifications IN (0,1)), volume integer, deleted boolean NOT NULL CHECK (deleted IN (0,1)), channel_id INTEGER REFERENCES channel(id), is_main_in_channel boolean NOT NULL CHECK (is_main_in_channel IN (0,1)), creator_id INTEGER REFERENCES user(id))"); t = await this.runSql(t, "CREATE TABLE message (id integer primary key, time integer, content text, symbol text, deleted boolean NOT NULL CHECK (deleted IN (0,1)), edited integer, room_id integer REFERENCES room(id), user_id integer REFERENCES user(id), status text, parent_message_id INTEGER REFERENCES message(id) ON UPDATE CASCADE, thread_messages_count INTEGER)"); t = await this.runSql(t, "CREATE TABLE file (id integer primary key, sending boolean NOT NULL CHECK (sending IN (0,1)), preview_file_id integer, file_id integer, server_id integer UNIQUE, symbol text, url text, message_id INTEGER REFERENCES message(id) ON UPDATE CASCADE , type text, preview text)"); t = await this.runSql(t, "CREATE TABLE tag (id integer primary key, user_id INTEGER REFERENCES user(id), message_id INTEGER REFERENCES message(id) ON UPDATE CASCADE, symbol text)"); - t = await this.runSql(t, "CREATE TABLE settings (user_id integer primary key, embedded_youtube boolean NOT NULL CHECK (embedded_youtube IN (0,1)), highlight_code boolean NOT NULL CHECK (highlight_code IN (0,1)), incoming_file_call_sound boolean NOT NULL CHECK (incoming_file_call_sound IN (0,1)), message_sound boolean NOT NULL CHECK (message_sound IN (0,1)), online_change_sound boolean NOT NULL CHECK (online_change_sound IN (0,1)), send_logs boolean NOT NULL CHECK (send_logs IN (0,1)), suggestions boolean NOT NULL CHECK (suggestions IN (0,1)), theme text, logs text, show_when_i_typing boolean NOT NULL CHECK (show_when_i_typing IN (0,1)))"); - t = await this.runSql(t, "CREATE TABLE profile (user_id integer primary key, user text, name text, city text, surname text, email text, birthday text, contacts text, image text, sex integer NOT NULL CHECK (sex IN (0,1,2)))"); + t = await this.runSql(t, "CREATE TABLE settings (user_id integer primary key, embedded_youtube boolean NOT NULL CHECK (embedded_youtube IN (0,1)), highlight_code boolean NOT NULL CHECK (highlight_code IN (0,1)), incoming_file_call_sound boolean NOT NULL CHECK (incoming_file_call_sound IN (0,1)), message_sound boolean NOT NULL CHECK (message_sound IN (0,1)), online_change_sound boolean NOT NULL CHECK (online_change_sound IN (0,1)), suggestions boolean NOT NULL CHECK (suggestions IN (0,1)), theme text, logs text, show_when_i_typing boolean NOT NULL CHECK (show_when_i_typing IN (0,1)))"); + t = await this.runSql(t, "CREATE TABLE profile (user_id integer primary key, username text, name text, city text, surname text, email text, birthday text, contacts text, thumbnail text, sex text NOT NULL CHECK (sex IN ('MALE','FEMALE','OTHER')))"); t = await this.runSql(t, "CREATE TABLE room_users (room_id INTEGER REFERENCES room(id), user_id INTEGER REFERENCES user(id))"); this.logger.log("DatabaseWrapper has been initialized")(); return null; @@ -121,7 +111,7 @@ export default class DatabaseWrapper implements IStorage { t, "insert or replace into message (id, time, content, symbol, deleted, edited, room_id, user_id, status, parent_message_id, thread_messages_count) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [message.id, message.time, message.content, message.symbol || null, message.deleted ? 1 : 0, message.edited, message.roomId, message.userId, message.status, message.parentMessage || null, message.threadMessagesCount], - (t, d) => { + (t) => { for (const k in message.files) { const f = message.files[k]; this.executeSql(t, "insert or replace into file (server_id, file_id, preview_file_id, symbol, url, message_id, type, preview, sending) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", [f.serverId, f.fileId, f.previewFileId, k, f.url, message.id, f.type, f.preview, f.sending ? 1 : 0])(); @@ -132,7 +122,7 @@ export default class DatabaseWrapper implements IStorage { for (const k in message.tags) { const userId = message.tags[k]; this.executeSql(t, "insert into tag (user_id, message_id, symbol) values (?, ?, ?)", [userId, message.id, k])(); - // This.executeSql(t, 'delete from file where message_id = ? and symbol = ? ', [message.id, k], (t) => { + // This.executeSql(t,G 'delete from file where message_id = ? and symbol = ? ', [message.id, k], (t) => { // })(); } @@ -245,8 +235,8 @@ export default class DatabaseWrapper implements IStorage { public getInsertUser(user: UserModel): QueryObject { return [ - "insert or replace into user (id, user, sex, deleted, country_code, country, region, city, last_time_online, thumbnail) values (?, ?, ?, 0, ?, ?, ?, ?, ?, ?)", - [user.id, user.user, convertSexToNumber(user.sex), user.location.countryCode, user.location.country, user.location.region, user.location.city, user.lastTimeOnline, user.image], + "insert or replace into user (id, username, sex, deleted, country_code, country, region, city, last_time_online, thumbnail) values (?, ?, ?, 0, ?, ?, ?, ?, ?, ?)", + [user.id, user.username, user.sex, user.location.countryCode, user.location.country, user.location.region, user.location.city, user.lastTimeOnline, user.thumbnail], ]; } @@ -263,7 +253,7 @@ export default class DatabaseWrapper implements IStorage { }); } - public setMessagesStatus(messagesIds: number[], status: MessageStatus): void { + public setMessagesStatus(messagesIds: number[], status: MessageStatusModel): void { this.write((t) => { this.executeSql(t, `update message set status = ? where id in ${this.idsToString(messagesIds)}`, [status])(); }); @@ -271,13 +261,13 @@ export default class DatabaseWrapper implements IStorage { public setUserProfile(user: CurrentUserInfoModel) { this.write((t) => { - this.executeSql(t, "insert or replace into profile (user_id, user, name, city, surname, email, birthday, contacts, sex, image) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [user.userId, user.user, user.name, user.city, user.surname, user.email, user.birthday, user.contacts, convertStringSexToNumber(user.sex), user.image])(); + this.executeSql(t, "insert or replace into profile (user_id, username, name, city, surname, email, birthday, contacts, sex, thumbnail) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [user.id, user.username, user.name, user.city, user.surname, user.email, user.birthday, user.contacts, user.sex, user.thumbnail])(); }); } public setUserSettings(settings: CurrentUserSettingsModel) { this.write((t) => { - this.executeSql(t, "insert or replace into settings (user_id, embedded_youtube, highlight_code, incoming_file_call_sound, message_sound, online_change_sound, send_logs, suggestions, theme, logs, show_when_i_typing) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [1, settings.embeddedYoutube ? 1 : 0, settings.highlightCode ? 1 : 0, settings.incomingFileCallSound ? 1 : 0, settings.messageSound ? 1 : 0, settings.onlineChangeSound ? 1 : 0, settings.sendLogs ? 1 : 0, settings.suggestions ? 1 : 0, settings.theme, settings.logs, settings.showWhenITyping ? 1 : 0])(); + this.executeSql(t, "insert or replace into settings (user_id, embedded_youtube, highlight_code, incoming_file_call_sound, message_sound, online_change_sound, suggestions, theme, logs, show_when_i_typing) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [1, settings.embeddedYoutube ? 1 : 0, settings.highlightCode ? 1 : 0, settings.incomingFileCallSound ? 1 : 0, settings.messageSound ? 1 : 0, settings.onlineChangeSound ? 1 : 0, settings.suggestions ? 1 : 0, settings.theme, settings.logs, settings.showWhenITyping ? 1 : 0])(); }); } @@ -320,7 +310,7 @@ export default class DatabaseWrapper implements IStorage { public updateRoom(r: RoomSettingsModel) { this.write((t) => { - this.executeSql(t, "update room set name = ?, volume = ?, notifications = ?, p2p = ?, creator = ?, is_main_in_channel = ?, channel_id = ? where id = ? ", [r.name, r.volume, r.notifications ? 1 : 0, r.p2p ? 1 : 0, r.creator, r.isMainInChannel ? 1 : 0, r.channelId, r.id])(); + this.executeSql(t, "update room set name = ?, volume = ?, notifications = ?, p2p = ?, creator_id = ?, is_main_in_channel = ?, channel_id = ? where id = ? ", [r.name, r.volume, r.notifications ? 1 : 0, r.p2p ? 1 : 0, r.creatorId, r.isMainInChannel ? 1 : 0, r.channelId, r.id])(); }); } @@ -342,9 +332,9 @@ export default class DatabaseWrapper implements IStorage { this.write((t) => { this.executeMultiple( t, - Object.keys(m.fileIds).map((symb) => [ + m.files.map((file) => [ "update file set file_id = ?, preview_file_id = ? where symbol = ? and message_id = ?", - [m.fileIds[symb].fileId, m.fileIds[symb].previewFileId ?? null, symb, m.messageId], + [file.id, file.previewId ?? null, file.symbol, m.messageId], ]), )(); }); @@ -431,13 +421,13 @@ export default class DatabaseWrapper implements IStorage { const roomsDict: RoomDictModel = {}; dbRooms.forEach((r: RoomDB) => { const rm: RoomModel = getRoomsBaseDict({ - roomId: r.id, + id: r.id, p2p: convertToBoolean(r.p2p), notifications: convertToBoolean(r.notifications), isMainInChannel: convertToBoolean(r.is_main_in_channel), name: r.name, channelId: r.channel_id, - roomCreatorId: r.creator, + creatorId: r.creator_id, users: [], volume: r.volume, }); @@ -469,9 +459,9 @@ export default class DatabaseWrapper implements IStorage { threadMessagesCount: m.thread_messages_count, deleted: convertToBoolean(m.deleted), tags, - transfer: m.status === "sending" ? { + transfer: m.status === MessageStatusInner.SENDING ? { error: null, - xhr: null, + abortFn: null, upload: null, } : null, files: {}, @@ -501,7 +491,7 @@ export default class DatabaseWrapper implements IStorage { serverId: f.server_id, previewFileId: f.preview_file_id, sending: convertToBoolean(f.sending), - type: f.type as BlobType, + type: f.type as ImageType, preview: f.preview, }; @@ -517,9 +507,9 @@ export default class DatabaseWrapper implements IStorage { const user: UserModel = { id: u.id, lastTimeOnline: u.last_time_online, - sex: convertNumberToSex(u.sex), - image: u.thumbnail, - user: u.user, + sex: u.sex, + thumbnail: u.thumbnail, + username: u.username, location: { region: u.region, city: u.city, @@ -536,9 +526,9 @@ export default class DatabaseWrapper implements IStorage { const channelsDict: ChannelsDictModel = {}; dbChannels.forEach((c: ChannelDB) => { const chm: ChannelModel = getChannelDict({ - channelId: c.id, - channelName: c.name, - channelCreatorId: c.creator, + id: c.id, + name: c.name, + creatorId: c.creator_id, }); channelsDict[c.id] = chm; }); @@ -553,7 +543,6 @@ export default class DatabaseWrapper implements IStorage { messageSound: convertToBoolean(dbSettings.message_sound), showWhenITyping: convertToBoolean(dbSettings.show_when_i_typing), onlineChangeSound: convertToBoolean(dbSettings.online_change_sound), - sendLogs: convertToBoolean(dbSettings.send_logs), suggestions: convertToBoolean(dbSettings.suggestions), theme: dbSettings.theme, logs: dbSettings.logs, @@ -562,16 +551,16 @@ export default class DatabaseWrapper implements IStorage { private getProfileModel(dbProfile: ProfileDB): CurrentUserInfoModel { return { - sex: convertSexToString(dbProfile.sex), + sex: dbProfile.sex, contacts: dbProfile.contacts, - image: dbProfile.image, + thumbnail: dbProfile.thumbnail, birthday: dbProfile.birthday, email: dbProfile.email, surname: dbProfile.surname, city: dbProfile.city, name: dbProfile.name, - userId: dbProfile.user_id, - user: dbProfile.user, + id: dbProfile.user_id, + username: dbProfile.username, }; } @@ -663,10 +652,10 @@ export default class DatabaseWrapper implements IStorage { } private getSetRoomQuery(t: SQLTransaction, room: RoomSettingsModel): QueryObject { - return ["insert or replace into room (id, name, notifications, volume, deleted, channel_id, p2p, creator, is_main_in_channel) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", [room.id, room.name, room.notifications ? 1 : 0, room.volume, 0, room.channelId, room.p2p ? 1 : 0, room.creator, room.isMainInChannel ? 1 : 0]]; + return ["insert or replace into room (id, name, notifications, volume, deleted, channel_id, p2p, creator_id, is_main_in_channel) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", [room.id, room.name, room.notifications ? 1 : 0, room.volume, 0, room.channelId, room.p2p ? 1 : 0, room.creatorId, room.isMainInChannel ? 1 : 0]]; } private setChannel(t: SQLTransaction, channel: ChannelModel) { - this.executeSql(t, "insert or replace into channel (id, name, deleted, creator) values (?, ?, ?, ?)", [channel.id, channel.name, 0, channel.creator])(); + this.executeSql(t, "insert or replace into channel (id, name, deleted, creator_id) values (?, ?, ?, ?)", [channel.id, channel.name, 0, channel.creatorId])(); } } diff --git a/frontend/src/ts/classes/DefaultStore.ts b/frontend/src/ts/classes/DefaultStore.ts index 6137001d1..934d815db 100644 --- a/frontend/src/ts/classes/DefaultStore.ts +++ b/frontend/src/ts/classes/DefaultStore.ts @@ -1,3 +1,5 @@ +import type {Gender} from "@common/model/enum/gender"; +import {MessageStatus} from "@common/model/enum/message.status"; import Vuex from "vuex"; import loggerFactory from "@/ts/instances/loggerFactory"; @@ -8,11 +10,10 @@ import type { CurrentUserInfoModel, IncomingCallModel, Location, - MessageStatus, + MessageStatusModel, PastingTextAreaElement, RoomDictModel, SendingFileTransfer, - SexModelString, UserDictModel, } from "@/ts/types/model"; import { @@ -23,6 +24,7 @@ import { GrowlModel, GrowlType, MessageModel, + MessageStatusInner, ReceivingFile, RoomModel, RoomSettingsModel, @@ -63,22 +65,11 @@ import { ShareIdentifier, StringIdentifier, } from "@/ts/types/types"; -import { - SetStateFromStorage, - SetStateFromWS, -} from "@/ts/types/dto"; +import {SetStateFromStorage, SetStateFromWS} from "@/ts/types/dto"; import {encodeHTML} from "@/ts/utils/htmlApi"; -import { - ACTIVE_ROOM_ID_LS_NAME, - ALL_ROOM_ID, - SHOW_I_TYPING_INTERVAL, -} from "@/ts/utils/consts"; -import { - Action, - Module, - Mutation, - VuexModule, -} from "vuex-module-decorators"; +import {ACTIVE_ROOM_ID_LS_NAME, ALL_ROOM_ID, SHOW_I_TYPING_INTERVAL} from "@/ts/utils/consts"; +import {Action, Module, Mutation, VuexModule} from "vuex-module-decorators"; + const logger = loggerFactory.getLogger("store"); @@ -171,7 +162,7 @@ export class DefaultStore extends VuexModule { } get userName(): (id: number) => string { - return (id: number): string => this.allUsersDict[id].user; + return (id: number): string => this.allUsersDict[id].username; } get privateRooms(): RoomModel[] { @@ -286,7 +277,7 @@ export class DefaultStore extends VuexModule { rooms: [], mainRoom: null!, name: channel.name, - creator: channel.creator, + creatorId: channel.creatorId, }; } if (current.isMainInChannel) { @@ -318,7 +309,7 @@ export class DefaultStore extends VuexModule { } get myId(): number | null { - return this.userInfo?.userId ?? null; + return this.userInfo?.id ?? null; } get privateRoomsUsersIds(): PrivateRoomsIds { @@ -406,7 +397,7 @@ export class DefaultStore extends VuexModule { public setUploadXHR(payload: SetUploadXHR) { const message = this.roomsDict[payload.roomId].messages[payload.messageId]; if (message.transfer) { - message.transfer.xhr = payload.xhr; + message.transfer.abortFn = payload.abortFunction; } else { throw Error(`Transfer upload doesn't exist ${JSON.stringify(this.state)} ${JSON.stringify(payload)}`); } @@ -603,7 +594,7 @@ export class DefaultStore extends VuexModule { }: { roomId: number; messagesIds: number[]; - status: MessageStatus; + status: MessageStatusModel; }, ) { const ids = Object.values(this.roomsDict[roomId].messages).filter((m) => messagesIds.includes(m.id)). @@ -640,9 +631,9 @@ export class DefaultStore extends VuexModule { if (!mm.files) { throw Error(`Message ${payload.messageId} in room ${payload.roomId} doesn't have files`); } - Object.keys(payload.fileIds).forEach((symb) => { - mm.files![symb].fileId = payload.fileIds[symb].fileId || null; - mm.files![symb].previewFileId = payload.fileIds[symb].previewFileId || null; + payload.files.forEach((file) => { + mm.files![file.symbol].fileId = file.id || null; + mm.files![file.symbol].previewFileId = file.previewId || null; }); this.storage.updateFileIds(payload); } @@ -658,7 +649,7 @@ export class DefaultStore extends VuexModule { */ if (m.parentMessage && om[m.parentMessage] && !om[m.id] && // And this message is not from us (otherwise we already increased message count when sending it and storing in store) - !(m.userId === this.userInfo?.userId && m.id > 0)) { + !(m.userId === this.userInfo?.id && m.id > 0)) { om[m.parentMessage].threadMessagesCount++; this.storage.setThreadMessageCount(m.parentMessage, om[m.parentMessage].threadMessagesCount); } @@ -706,8 +697,8 @@ export class DefaultStore extends VuexModule { const markSendingIds: number[] = []; m.messagesId.forEach((messageId) => { const message = this.roomsDict[m.roomId].messages[messageId]; - if (message.status === "sending") { - message.status = "on_server"; + if (message.status === MessageStatusInner.SENDING) { + message.status = MessageStatus.ON_SERVER; markSendingIds.push(messageId); } }); @@ -890,19 +881,19 @@ export class DefaultStore extends VuexModule { } @Mutation - public setUser(user: {id: number; sex: SexModelString; user: string; image: string}) { - this.allUsersDict[user.id].user = user.user; + public setUser(user: {id: number; sex: Gender; username: string; thumbnail: string}) { + this.allUsersDict[user.id].username = user.username; this.allUsersDict[user.id].sex = user.sex; - this.allUsersDict[user.id].image = user.image; + this.allUsersDict[user.id].thumbnail = user.thumbnail; this.storage.saveUser(this.allUsersDict[user.id]); } @Mutation public setUserInfo(userInfo: CurrentUserInfoWoImage) { - const image: string | null = this.userInfo?.image || null; + const thumbnail: string | null = this.userInfo?.thumbnail || null; const newUserInfo: CurrentUserInfoModel = { ...userInfo, - image, + thumbnail, }; this.userInfo = newUserInfo; this.storage.setUserProfile(this.userInfo); @@ -915,8 +906,8 @@ export class DefaultStore extends VuexModule { } @Mutation - public setUserImage(image: string) { - this.userInfo!.image = image; + public setUserImage(thumbnail: string) { + this.userInfo!.thumbnail = thumbnail; this.storage.setUserProfile(this.userInfo!); } diff --git a/frontend/src/ts/classes/Fetch.ts b/frontend/src/ts/classes/Fetch.ts index 93fd94d00..a2f945138 100644 --- a/frontend/src/ts/classes/Fetch.ts +++ b/frontend/src/ts/classes/Fetch.ts @@ -1,99 +1,131 @@ -import type {SessionHolder} from "@/ts/types/types"; +import type {PostData, UploadData} from "@/ts/types/types"; +import type {Logger} from "lines-logger"; +import loggerFactory from "@/ts/instances/loggerFactory"; +import {CONNECTION_ERROR} from "@/ts/utils/consts"; -/** - * @param params : object dict of params or DOM form - * @param callback : function calls on response - * @param url : string url to post - * @param formData : form in canse form is used - * - */ +export default class Fetch { + protected httpLogger: Logger; -export default class Fetch /* Extends Http*/ { - public constructor(apiUrl: string, sessionHolder: SessionHolder) { - // Super(apiUrl, sessionHolder); + protected getHeaders: () => Record; + + private readonly url: string; + + public constructor(url: string, getHeaders: () => Record) { + this.httpLogger = loggerFactory.getLogger("http"); + this.url = url; + this.getHeaders = getHeaders; + } + + public async doGet(url: string, onSetAbortFunction?: (c: () => void) => void): Promise { + const {signal, fetchUrl, headers} = this.prepareRequest(url, onSetAbortFunction); + const response = await fetch(fetchUrl, { + method: "GET", + mode: "cors", + headers, + signal, + }); + return this.processResponse(response); + } + + public async doPost(d: PostData): Promise { + const {headers, signal, fetchUrl} = this.prepareRequest(d.url, d.onSetAbortFunction, { + "Content-Type": "application/json", + }); + const response = await fetch(fetchUrl, { + method: "POST", + mode: "cors", + signal, + body: JSON.stringify(d.params), + headers, + }); + return this.processResponse(response); + } + + + public async upload({url, data, onSetAbortFunction, onProgress}: UploadData): Promise { + /* + * https://ilikekillnerds.com/2020/09/file-upload-progress-with-the-fetch-api-is-coming/ + * https://chromestatus.com/feature/5274139738767360 + * fetch api doesnt support progress api yet + */ + return new Promise((resolve, reject) => { + const r = new XMLHttpRequest(); + r.addEventListener("load", () => { + try { + const response = JSON.parse(r.response); + if (r.status < 200 || r.status >= 300) { + this.processException(response); + } else { + resolve(response); + return; + } + } catch (e) { + reject(e); + } + }); + r.addEventListener("error", () => { + reject(CONNECTION_ERROR); + }); + if (onSetAbortFunction) { + onSetAbortFunction(() => { + r.abort(); + }); + } + if (onProgress) { + r.upload.addEventListener("progress", (evt) => { + if (evt.lengthComputable) { + onProgress(evt.loaded); + } + }); + } + r.open("POST", `${this.url}${url}`); + Object.entries(this.getHeaders()).forEach(([k, v]) => { + r.setRequestHeader(k, v); + }); + const form = new FormData(); + Object.entries(data).forEach(([key, value]) => { + form.append(key, value); + }); + r.send(form); + }); + } + + private async processResponse(response: Response): Promise { + const body = await response.json(); + if (response.ok) { + return body as T; + } + this.processException(body); } - // - // /** - // * Loads file from server on runtime */ - // Public doGet(fileUrl: string, callback: ErrorCB, isJsonDecoded: boolean = false) { - // FileUrl = this.getUrl(fileUrl); - // This.httpLogger.log('GET out {}', fileUrl)(); - // Let regexRes = /\.(\w+)(\?.*)?$/.exec(fileUrl); - // Let fileType = regexRes != null && regexRes.length === 3 ? regexRes[1] : null; - // Let xobj = new XMLHttpRequest(); - // // special for IE - // If (xobj.overrideMimeType) { - // Xobj.overrideMimeType('application/json'); - // } - // Xobj.open('GET', fileUrl, true); // Replace 'my_data' with the path to your file - // - // Xobj.onreadystatechange = this.getOnreadystatechange( - // Xobj, - // FileUrl, - // IsJsonDecoded || fileType === 'json', - // 'GET', - // Callback - // ); - // Xobj.send(null); - // } - // - // Async asyncPost(d: PostData) { - // Let config: RequestInit = { - // Cache: 'no-cache', - // Headers: { - // 'session-id': this.sessionHolder.session - // }, - // }; - // If (d.isJsonEncoded) { - // Config.headers['Content-Type'] = 'application/json'; - // } else { - // Let body; - // Let logOut: string = ''; - // Body = d.formData ? d.formData : new FormData(); - // If (d.params) { - // For (let key in d.params) { - // Body.append(key, d.params[key]); - // } - // } - // If (body.entries) { - // Let entries = body.entries(); - // If (entries && entries.next) { - // Let d; - // While (d = entries.next()) { - // If (d.done) { - // Break; - // } - // LogOut += `${d.value[0]}= ${d.value[1]};`; - // } - // } - // } - // } - // Await fetch(d.url, config); - // } - // - // DoPost(d: PostData): XMLHttpRequest { - // Let r: XMLHttpRequest = new XMLHttpRequest(); - // R.onreadystatechange = this.getOnreadystatechange(r, d.url, d.isJsonDecoded, 'POST', d.cb); - // - // Let url = this.getUrl(d.url); - // - // R.open('POST', url, true); - // Let data; - // Let logOut: String = ''; - // If (d.isJsonEncoded) { - // Data = JSON.stringify(d.params); - // R.setRequestHeader('Content-Type', 'application/json'); - // } else { - // /*Firefox doesn't accept null*/ - // - // } - // - // This.httpLogger.log('POST out {} ::: {} ::: {}', url, d.params, logOut)(); - // If (d.process) { - // D.process(r); - // } - // R.send(data); - // Return r; - // } + private processException(body: any): never { + if (typeof body?.message === "string") { + throw Error(body.message); + } else if (body?.message?.length && typeof body.message[0] === "string") { + throw Error(body.message[0]); + } else if (typeof body?.error === "string") { + throw Error(body.error); + } else { + throw Error("Network error"); + } + } + + private prepareRequest(url: string, onSetAbortFunction?: (c: () => void) => void, headers: Record = {}) { + let signal = null; + if (onSetAbortFunction) { + const controller = new AbortController(); + signal = controller.signal; + onSetAbortFunction(() => { + controller.abort(); + }); + } + Object.entries(this.getHeaders()).forEach(([k, v]) => { + headers[k] = v; + }); + return { + signal, + fetchUrl: `${this.url}${url}`, + headers, + }; + } } diff --git a/frontend/src/ts/classes/Http.ts b/frontend/src/ts/classes/Http.ts deleted file mode 100644 index 0f8a1bb3c..000000000 --- a/frontend/src/ts/classes/Http.ts +++ /dev/null @@ -1,35 +0,0 @@ -import loggerFactory from "@/ts/instances/loggerFactory"; -import type { - GetData, - PostData, - SessionHolder, -} from "@/ts/types/types"; -import type {Logger} from "lines-logger"; - -export default abstract class Http { - protected httpLogger: Logger; - - protected sessionHolder: SessionHolder; - - public constructor(sessionHolder: SessionHolder) { - this.sessionHolder = sessionHolder; - this.httpLogger = loggerFactory.getLogger("http"); - } - - public abstract doGet(d: GetData): Promise; - - public abstract doPost(d: PostData): Promise; - - public async loadJs(fullFileUrlWithProtocol: string): Promise { - return new Promise((resolve, reject) => { - this.httpLogger.log("GET out {}", fullFileUrlWithProtocol)(); - const fileRef = document.createElement("script"); - fileRef.setAttribute("type", "text/javascript"); - fileRef.setAttribute("src", fullFileUrlWithProtocol); - - document.getElementsByTagName("head")[0].appendChild(fileRef); - fileRef.onload = resolve; - fileRef.onerror = reject; - }); - } -} diff --git a/frontend/src/ts/classes/LocalStorage.ts b/frontend/src/ts/classes/LocalStorage.ts index bf79f60c6..957acc41f 100644 --- a/frontend/src/ts/classes/LocalStorage.ts +++ b/frontend/src/ts/classes/LocalStorage.ts @@ -1,18 +1,12 @@ import loggerFactory from "@/ts/instances/loggerFactory"; -import type { - IStorage, - SetFileIdsForMessage, - SetRoomsUsers, -} from "@/ts/types/types"; -import type { - ChannelModel, +import type {IStorage, SetFileIdsForMessage, SetRoomsUsers} from "@/ts/types/types"; +import type {ChannelModel, CurrentUserInfoModel, CurrentUserSettingsModel, MessageModel, - MessageStatus, RoomSettingsModel, UserModel, -} from "@/ts/types/model"; + MessageStatusModel} from "@/ts/types/model"; import type {Logger} from "lines-logger"; interface LocalStorageMessage { @@ -108,7 +102,7 @@ export default class LocalStorage implements IStorage { public setUsers(users: UserModel[]) { } - public setMessagesStatus(messagesIds: number[], status: MessageStatus) { + public setMessagesStatus(messagesIds: number[], status: MessageStatusModel) { } public saveUser(users: UserModel) { diff --git a/frontend/src/ts/classes/MediaCapture.ts b/frontend/src/ts/classes/MediaCapture.ts index 94dd1a6ea..26af4d771 100644 --- a/frontend/src/ts/classes/MediaCapture.ts +++ b/frontend/src/ts/classes/MediaCapture.ts @@ -1,10 +1,7 @@ import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; import {stopVideo} from "@/ts/utils/htmlApi"; -import type { - permissions_type, - PlatformUtil, -} from "@/ts/types/model"; +import type {permissions_type, PlatformUtil} from "@/ts/types/model"; export default class MediaCapture { private readonly isRecordingVideo: boolean; diff --git a/frontend/src/ts/classes/NotificationHandler.ts b/frontend/src/ts/classes/NotificationHandler.ts index a20f952bf..17e974e9a 100644 --- a/frontend/src/ts/classes/NotificationHandler.ts +++ b/frontend/src/ts/classes/NotificationHandler.ts @@ -1,36 +1,32 @@ +import type {InternetAppearMessage} from "@/ts/types/messages/inner/internet.appear"; import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; import {extractError} from "@/ts/utils/pureFunctions"; -import type Api from "@/ts/message_handlers/Api"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type HttpApi from "@/ts/message_handlers/HttpApi"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; import { + CONNECTION_ERROR, GIT_HASH, IS_DEBUG, SERVICE_WORKER_URL, SERVICE_WORKER_VERSION_LS_NAME, } from "@/ts/utils/consts"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {InternetAppearMessage} from "@/ts/types/messages/innerMessages"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; + import type {MainWindow} from "@/ts/classes/MainWindow"; import type Subscription from "@/ts/classes/Subscription"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {DestroyPeerConnectionMessage} from "@/ts/types/messages/inner/destroy.peer.connection"; -export default class NotifierHandler extends MessageHandler { +export default class NotifierHandler { protected readonly logger: Logger; - protected readonly handlers: HandlerTypes = { - internetAppear: > this.internetAppear, - }; - private readonly mainWindow: MainWindow; private readonly popedNotifQueue: Notification[] = []; + private retryFcm: Function | null = null; /* This is required to know if this tab is the only one and don't spam with same notification for each tab*/ private serviceWorkedTried = false; @@ -43,7 +39,7 @@ export default class NotifierHandler extends MessageHandler { private readonly store: DefaultStore; - private readonly api: Api; + private readonly api: HttpApi; private readonly browserVersion: string; @@ -51,14 +47,13 @@ export default class NotifierHandler extends MessageHandler { private readonly isMobile: boolean; - private readonly ws: WsHandler; + private readonly ws: WsApi; private readonly documentTitle: string; private readonly sub: Subscription; - public constructor(api: Api, browserVersion: string, isChrome: boolean, isMobile: boolean, ws: WsHandler, store: DefaultStore, mainWindow: MainWindow, sub: Subscription) { - super(); + public constructor(api: HttpApi, browserVersion: string, isChrome: boolean, isMobile: boolean, ws: WsApi, store: DefaultStore, mainWindow: MainWindow, sub: Subscription) { this.api = api; this.browserVersion = browserVersion; this.isChrome = isChrome; @@ -74,10 +69,14 @@ export default class NotifierHandler extends MessageHandler { this.onFocus(null); } - public async internetAppear(p: InternetAppearMessage) { + @Subscribe() + public async internetAppear() { if (!this.serviceWorkedTried) { await this.tryAgainRegisterServiceWorker(); } + if (this.retryFcm) { + this.retryFcm(); + } } public replaceIfMultiple(data: {title: string; options: NotificationOptions}) { @@ -249,7 +248,18 @@ export default class NotifierHandler extends MessageHandler { if (subscription.endpoint && subscription.endpoint.startsWith("https://fcm.googleapis.com/fcm/send")) { const registrationId = subscription.endpoint.split("/").pop(); - await this.api.registerFCB(registrationId, this.browserVersion, this.isMobile); + this.retryFcm = async() => { + try { + await this.api.restApi.registerFCM(registrationId, this.browserVersion, this.isMobile); + this.retryFcm = null; + } catch (e) { + if (e !== CONNECTION_ERROR) { + this.retryFcm = null; + } + throw e; + } + }; + this.retryFcm(); this.logger.log("Saved subscription to server")(); } else { this.logger.warn("Unsupported subscription type {}", subscription.endpoint)(); diff --git a/frontend/src/ts/classes/SessionHolderImpl.ts b/frontend/src/ts/classes/SessionHolderImpl.ts index 3c7e27618..56922eba5 100644 --- a/frontend/src/ts/classes/SessionHolderImpl.ts +++ b/frontend/src/ts/classes/SessionHolderImpl.ts @@ -2,6 +2,7 @@ import type {SessionHolder} from "@/ts/types/types"; export class SessionHolderImpl implements SessionHolder { private static readonly SESSION_KEY = "sessionId"; + private static readonly WS_CONNECTION_ID_KEY = "wsConnectionId"; get session(): string | null { return localStorage.getItem(SessionHolderImpl.SESSION_KEY); @@ -14,4 +15,16 @@ export class SessionHolderImpl implements SessionHolder { localStorage.removeItem(SessionHolderImpl.SESSION_KEY); } } + + get wsConnectionId(): string | null { + return sessionStorage.getItem(SessionHolderImpl.WS_CONNECTION_ID_KEY); + } + + set wsConnectionId(value: string | null) { + if (value) { + sessionStorage.setItem(SessionHolderImpl.WS_CONNECTION_ID_KEY, value); + } else { + sessionStorage.removeItem(SessionHolderImpl.WS_CONNECTION_ID_KEY); + } + } } diff --git a/frontend/src/ts/classes/Subscription.ts b/frontend/src/ts/classes/Subscription.ts index 70cde41b2..e295da2ef 100644 --- a/frontend/src/ts/classes/Subscription.ts +++ b/frontend/src/ts/classes/Subscription.ts @@ -1,16 +1,14 @@ +import type {DefaultWsInMessage, HandlerName} from "@common/ws/common"; +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; -import type { - DefaultInMessage, - HandlerName, - IMessageHandler, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {DefaultInnerSystemMessage} from "@/ts/types/messages/innerMessages"; + export default class Subscription { // TODO sub should unsubscribe from some events on logout - private suscribers: Partial> = {}; + private suscribers: Partial> = {}; private readonly logger: Logger; @@ -34,7 +32,7 @@ export default class Subscription { return this.suscribers[channel]?.length ?? 0; } - public subscribe(channel: HandlerName, messageHandler: IMessageHandler) { + public subscribe(channel: HandlerName, messageHandler: any) { if (!this.suscribers[channel]) { this.suscribers[channel] = []; } @@ -44,7 +42,7 @@ export default class Subscription { } } - public unsubscribe(channel: HandlerName, handler: IMessageHandler) { + public unsubscribe(channel: HandlerName, handler: any) { const c = this.suscribers[channel]; if (c) { @@ -61,11 +59,11 @@ export default class Subscription { this.logger.error("Unable to find channel to delete {}", channel)(); } - public notify>(message: T): boolean { + public notify>(message: T): boolean { this.logger.debug("notifing {}", message)(); if (message.handler === "*") { Object.values(this.suscribers).forEach((channel) => { - channel.forEach((h: IMessageHandler) => { + channel.forEach((h: any) => { if (h.getHandler(message)) { this.logger.debug(`Notifying (${message.handler}).[${message.action}] {}`, h)(); h.handle(message); @@ -74,14 +72,14 @@ export default class Subscription { }); return true; } else if (this.suscribers[message.handler]?.length) { - this.suscribers[message.handler]!.forEach((h: IMessageHandler) => { + this.suscribers[message.handler]!.forEach((h: any) => { this.logger.debug(`Notifying (${message.handler}).[${message.action}] {}`, h)(); h.handle(message); }); return true; } - if (!(message as DefaultInnerSystemMessage).allowZeroSubscribers) { + if (!(message as DefaultInnerSystemMessage).allowZeroSubscribers) { this.logger.error("Can't handle message {} because no suscribers found, available suscribers {}", message, this.suscribers)(); } diff --git a/frontend/src/ts/classes/Xhr.ts b/frontend/src/ts/classes/Xhr.ts deleted file mode 100644 index 6c9be6ea4..000000000 --- a/frontend/src/ts/classes/Xhr.ts +++ /dev/null @@ -1,164 +0,0 @@ -import type { - GetData, - PostData, - SessionHolder, -} from "@/ts/types/types"; -import { - CONNECTION_ERROR, - RESPONSE_SUCCESS, -} from "@/ts/utils/consts"; -import Http from "@/ts/classes/Http"; -import {XHR_API_URL} from "@/ts/utils/runtimeConsts"; - -/** - * @param params : object dict of params or DOM form - * @param callback : function calls on response - * @param url : string url to post - * @param formData : form in canse form is used - * - */ - -export default class Xhr extends Http { - public constructor(sessionHolder: SessionHolder) { - super(sessionHolder); - } - - /** - * Loads file from server on runtime - */ - public async doGet(d: GetData): Promise { - return new Promise((resolve, reject) => { - const fileUrl = `${d.baseUrl ?? XHR_API_URL}${d.url}`; - this.httpLogger.log("GET out {}", fileUrl)(); - const xobj = new XMLHttpRequest(); - // Special for IE - if (xobj.overrideMimeType) { - xobj.overrideMimeType("application/json"); - } - xobj.open("GET", fileUrl, true); // Replace 'my_data' with the path to your file - if (!d.skipAuth) { - xobj.setRequestHeader("session-id", this.sessionHolder.session!); - } - xobj.onreadystatechange = this.getOnreadystatechange( - xobj, - d.isJsonDecoded || false, - d.checkOkString || false, - fileUrl, - undefined, - reject, - resolve, - ); - xobj.send(null); - }); - } - - public async doPost(d: PostData): Promise { - return new Promise((resolve, reject) => { - const r: XMLHttpRequest = new XMLHttpRequest(); - r.onreadystatechange = this.getOnreadystatechange( - r, - d.isJsonDecoded || false, - d.checkOkString || false, - d.url, - d.errorDescription, - reject, - resolve, - ); - - const url = `${XHR_API_URL}${d.url}`; - r.open("POST", url, true); - let data; - let logOut: string = ""; - if (d.isJsonEncoded) { - data = JSON.stringify(d.params); - r.setRequestHeader("Content-Type", "application/json"); - } else { - /* Firefox doesn't accept null*/ - data = d.formData ? d.formData : new FormData(); - - if (d.params) { - for (const key in d.params) { - data.append(key, d.params[key]); // TODO null putting? - } - } - if (data.entries) { - const entries = data.entries(); - if (entries && entries.next) { - let d; - while (d = entries.next()) { - if (d.done) { - break; - } - // @ts-expect-error: next-line - logOut += `${d.value[0]}= ${d.value[1]};`; - } - } - } - } - r.setRequestHeader("session-id", this.sessionHolder.session!); - - this.httpLogger.log("POST out {} ::: {} ::: {}", url, d.params, logOut)(); - if (d.process) { - d.process(r); - } - r.send(data); - - return r; - }); - } - - private getOnreadystatechange( - r: XMLHttpRequest, - isJsonDecoded: boolean, - checkOkString: boolean, - url: string, - errorDescription: string | undefined, - reject: (error: string) => void, - resolve: (data: T) => void, - ): () => void { - return () => { - if (r.readyState === 4) { - if (r.status === 200) { - this.httpLogger.log("{} in {} ::: {};", "GET", url, r.response)(); - } else { - this.httpLogger.error("{} out: {} ::: {}, status: {}", "GET", url, r.response, r.status)(); - } - - let error: string | null = null; - let data: T | null = null; - if (r.status === 0) { - error = CONNECTION_ERROR; - } else if (r.status === 200) { - if (isJsonDecoded) { - try { - data = JSON.parse(r.response); - } catch (e) { - error = `Unable to parse response ${e}`; - } - } else { - data = r.response; - } - } else if (r.status >= 400 && r.status < 500 && r.response) { - error = r.responseText; - } else if (r.status === 404) { - error = "Resource not found"; - } else if (r.status === 500) { - error = "Server error"; - } else { - error = "Unknown server error"; - } - if (checkOkString && !error && r.response !== RESPONSE_SUCCESS) { - error = r.response || "Invalid response"; - } - if (errorDescription && error) { - error = errorDescription + error; - } - if (error) { - reject(error); - } else { - resolve(data); // If else data, could not be null there - } - } - }; - } -} diff --git a/frontend/src/ts/utils/smileys.ts b/frontend/src/ts/classes/smileys.ts similarity index 100% rename from frontend/src/ts/utils/smileys.ts rename to frontend/src/ts/classes/smileys.ts diff --git a/frontend/src/ts/devices/AndroidPlatformUtils.ts b/frontend/src/ts/devices/AndroidPlatformUtils.ts index 9d1e208a3..e071643e2 100644 --- a/frontend/src/ts/devices/AndroidPlatformUtils.ts +++ b/frontend/src/ts/devices/AndroidPlatformUtils.ts @@ -1,8 +1,5 @@ import {IS_ANDROID} from "@/ts/utils/consts"; -import type { - permissions_type, - PlatformUtil, -} from "@/ts/types/model"; +import type {permissions_type, PlatformUtil} from "@/ts/types/model"; import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; diff --git a/frontend/src/ts/devices/WebPlatformUtils.ts b/frontend/src/ts/devices/WebPlatformUtils.ts index 62de3b69a..ea72affde 100644 --- a/frontend/src/ts/devices/WebPlatformUtils.ts +++ b/frontend/src/ts/devices/WebPlatformUtils.ts @@ -1,7 +1,4 @@ -import type { - permissions_type, - PlatformUtil, -} from "@/ts/types/model"; +import type {permissions_type, PlatformUtil} from "@/ts/types/model"; export class WebPlatformUtils implements PlatformUtil { public async askPermissions(...askedPermissions: permissions_type): Promise { diff --git a/frontend/src/ts/devices/electron.ts b/frontend/src/ts/devices/electron.ts index 701bd0b3d..5fe36406f 100644 --- a/frontend/src/ts/devices/electron.ts +++ b/frontend/src/ts/devices/electron.ts @@ -1,13 +1,6 @@ -import { - app, - BrowserWindow, -} from "electron"; +import {app, BrowserWindow} from "electron"; import * as constants from "@/ts/utils/consts"; -import { - ELECTRON_IGNORE_SSL, - ELECTRON_MAIN_FILE, - IS_DEBUG, -} from "@/ts/utils/consts"; +import {ELECTRON_IGNORE_SSL, ELECTRON_MAIN_FILE, IS_DEBUG} from "@/ts/utils/consts"; let mainWindow: BrowserWindow | null; diff --git a/frontend/src/ts/instances/loggerFactory.ts b/frontend/src/ts/instances/loggerFactory.ts index 354c7d693..691f5020a 100644 --- a/frontend/src/ts/instances/loggerFactory.ts +++ b/frontend/src/ts/instances/loggerFactory.ts @@ -1,8 +1,5 @@ import type {LogLevel} from "lines-logger"; import {LoggerFactory} from "lines-logger"; -import { - IS_DEBUG, - LOG_LEVEL_LS, -} from "@/ts/utils/consts"; +import {IS_DEBUG, LOG_LEVEL_LS} from "@/ts/utils/consts"; export default new LoggerFactory(localStorage.getItem(LOG_LEVEL_LS) as LogLevel || (IS_DEBUG ? "trace" : "error")); diff --git a/frontend/src/ts/instances/routerInstance.ts b/frontend/src/ts/instances/routerInstance.ts index 120df7bee..9154726a3 100644 --- a/frontend/src/ts/instances/routerInstance.ts +++ b/frontend/src/ts/instances/routerInstance.ts @@ -1,17 +1,15 @@ -import { - createRouter, - createWebHashHistory, -} from "vue-router"; -import sessionHolder from "@/ts/instances/sessionInstance"; +import type {LoginMessage, LoginMessageBody} from "@/ts/types/messages/inner/login"; +import type {LogoutMessage} from "@/ts/types/messages/inner/logout"; +import type {RouterNavigateMessage, RouterNavigateMessageBody} from "@/ts/types/messages/inner/router.navigate"; +import {createRouter, createWebHashHistory} from "vue-router"; import {store} from "@/ts/instances/storeInstance"; import MainPage from "@/vue/pages/MainPage.vue"; import ChannelsPage from "@/vue/pages/ChannelsPage.vue"; -import AuthPage from "@/vue/singup/AuthPage.vue"; -import ResetPassword from "@/vue/singup/ResetPassword.vue"; -import Login from "@/vue/singup/Login.vue"; -import SignUp from "@/vue/singup/SignUp.vue"; +import AuthPage from "@/vue/auth/AuthPage.vue"; +import ResetPassword from "@/vue/auth/ResetPassword.vue"; +import SignIn from "@/vue/auth/SignIn.vue"; +import SignUp from "@/vue/auth/SignUp.vue"; import UserProfile from "@/vue/pages/UserProfile.vue"; -import ReportIssue from "@/vue/pages/ReportIssue.vue"; import UserProfileChangePassword from "@/vue/pages/UserProfileChangePassword.vue"; import UserProfileImage from "@/vue/pages/UserProfileImage.vue"; import UserProfileInfo from "@/vue/pages/UserProfileInfo.vue"; @@ -19,11 +17,8 @@ import UserProfileSettings from "@/vue/pages/UserProfileSettings.vue"; import CreatePrivateRoom from "@/vue/pages/CreatePrivateRoom.vue"; import ViewProfilePage from "@/vue/pages/ViewProfilePage.vue"; import RoomSettings from "@/vue/pages/RoomSettings.vue"; -import ApplyResetPassword from "@/vue/singup/ApplyResetPassword.vue"; -import { - ACTIVE_ROOM_ID_LS_NAME, - ALL_ROOM_ID, -} from "@/ts/utils/consts"; +import ApplyResetPassword from "@/vue/auth/ApplyResetPassword.vue"; +import {ACTIVE_ROOM_ID_LS_NAME, ALL_ROOM_ID} from "@/ts/utils/consts"; import ConfirmMail from "@/vue/pages/ConfirmMail.vue"; import UserProfileChangeEmail from "@/vue/pages/UserProfileChangeEmail.vue"; import CreateChannel from "@/vue/pages/CreateChannel.vue"; @@ -31,23 +26,17 @@ import ChannelSettings from "@/vue/pages/ChannelSettings.vue"; import MessageHandler from "@/ts/message_handlers/MesageHandler"; import type {Logger} from "lines-logger"; import loggerFactory from "@/ts/instances/loggerFactory"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - LoginMessage, - LogoutMessage, - RouterNavigateMessage, -} from "@/ts/types/messages/innerMessages"; + import UserProfileOauthSettings from "@/vue/pages/UserProfileOauthSettings.vue"; import PainterPage from "@/vue/pages/PainterPage.vue"; import RoomUsersListPage from "@/vue/pages/RoomUsersListPage.vue"; import ChannelAddRoom from "@/vue/pages/ChannelAddRoom.vue"; import type Subscription from "@/ts/classes/Subscription"; +import type {SessionHolder} from "@/ts/types/types"; +import {Subscribe} from "@/ts/utils/pubsub"; -export function routerFactory(sub: Subscription) { +export function routerFactory(sub: Subscription, sessionHolder: SessionHolder) { const logger: Logger = loggerFactory.getLogger("router"); const router = createRouter({ history: createWebHashHistory(), // Seems like createWebHistory is really hard for sw to detect what to cache, so use this one @@ -57,7 +46,7 @@ export function routerFactory(sub: Subscription) { component: MainPage, beforeEnter: (to, from) => { if (!sessionHolder.session) { - return "/auth/login"; + return "/auth/sign-in"; } }, children: [ @@ -151,10 +140,6 @@ export function routerFactory(sub: Subscription) { component: CreatePrivateRoom, path: "/create-private-room", }, - { - component: ReportIssue, - path: "/report-issue", - }, ], }, { path: "/auth", @@ -162,11 +147,11 @@ export function routerFactory(sub: Subscription) { children: [ { path: "", - redirect: "/auth/login", + redirect: "/auth/sign-in", }, { - path: "login", - component: Login, + path: "sign-in", + component: SignIn, }, { path: "reset-password", @@ -182,7 +167,7 @@ export function routerFactory(sub: Subscription) { }, ], }, { - path: "/confirm_email", + path: "/confirm-email", component: ConfirmMail, }, { path: "/:catchAll(.*)", @@ -193,7 +178,7 @@ export function routerFactory(sub: Subscription) { router.beforeEach((to, from, next) => { if (to.matched[0]?.meta?.loginRequired && !sessionHolder.session) { - next("/auth/login"); + next("/auth/sign-in"); } else { if (to?.meta?.beforeEnter) { (to.meta.beforeEnter as any)(to, from, next); @@ -202,32 +187,31 @@ export function routerFactory(sub: Subscription) { } }); - sub.subscribe("router", new class RouterProcessor extends MessageHandler { + class RouterProcessor { protected readonly logger: Logger = logger; - protected readonly handlers: HandlerTypes = { - login: > this.login, - logout: > this.logout, - navigate: > this.navigate, - }; - - logout(a: LogoutMessage) { - router.replace("/auth/login"); + @Subscribe() + public logout() { + router.replace("/auth/sign-in"); } - navigate(a: RouterNavigateMessage) { + @Subscribe() + public navigate(a: RouterNavigateMessageBody) { if (router.currentRoute.value.path !== a.to) { router.replace(a.to); } } - login(a: LoginMessage) { + @Subscribe() + public login(a: LoginMessageBody) { if (!a.session) { throw Error(`Invalid session ${a.session}`); } sessionHolder.session = a.session; router.replace(`/chat/${ALL_ROOM_ID}`); } - }()); + } + + sub.subscribe("router", new RouterProcessor()); return router; } diff --git a/frontend/src/ts/instances/sessionInstance.ts b/frontend/src/ts/instances/sessionInstance.ts deleted file mode 100644 index 88d77d81b..000000000 --- a/frontend/src/ts/instances/sessionInstance.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type {SessionHolder} from "@/ts/types/types"; -import {SessionHolderImpl} from "@/ts/classes/SessionHolderImpl"; - -export default new SessionHolderImpl() as SessionHolder; diff --git a/frontend/src/ts/main.ts b/frontend/src/ts/main.ts index 8822456e2..7663ec81d 100644 --- a/frontend/src/ts/main.ts +++ b/frontend/src/ts/main.ts @@ -1,31 +1,22 @@ -import "@/assets/sass/common.sass"; import * as constants from "@/ts/utils/consts"; import * as runtimeConsts from "@/ts/utils/runtimeConsts"; -import { - browserVersion, - isChrome, - isMobile, - WS_API_URL, -} from "@/ts/utils/runtimeConsts"; +import {browserVersion, isChrome, isMobile, WS_API_URL} from "@/ts/utils/runtimeConsts"; // Should be after initStore import App from "@/vue/App.vue"; import {createApp} from "vue"; import {store} from "@/ts/instances/storeInstance"; import type {Logger} from "lines-logger"; import loggerFactory from "@/ts/instances/loggerFactory"; -import sessionHolder from "@/ts/instances/sessionInstance"; import type {App as VueApp} from "@vue/runtime-core"; import type {PlatformUtil} from "@/ts/types/model"; -import Xhr from "@/ts/classes/Xhr"; import "@/assets/icon.png"; // eslint-disable-line import/no-unassigned-import -import WsHandler from "@/ts/message_handlers/WsHandler"; +import WsApi from "@/ts/message_handlers/WsApi"; import WsMessageHandler from "@/ts/message_handlers/WsMessageHandler"; import DatabaseWrapper from "@/ts/classes/DatabaseWrapper"; import LocalStorage from "@/ts/classes/LocalStorage"; -import Api from "@/ts/message_handlers/Api"; +import HttpApi from "@/ts/message_handlers/HttpApi"; import NotifierHandler from "@/ts/classes/NotificationHandler"; -import type Http from "@/ts/classes/Http"; import WebRtcApi from "@/ts/webrtc/WebRtcApi"; import {routerFactory} from "@/ts/instances/routerInstance"; import {AudioPlayer} from "@/ts/classes/AudioPlayer"; @@ -37,12 +28,11 @@ import {RoomHandler} from "@/ts/message_handlers/RomHandler"; import {mainWindow} from "@/ts/instances/mainWindow"; import {SmileysApi} from "@/ts/utils/smileys"; import {loggerMixin} from "@/ts/utils/mixins"; -import { - switchDirective, - validityDirective, -} from "@/ts/utils/directives"; +import {switchDirective, validityDirective} from "@/ts/utils/directives"; import Subscription from "@/ts/classes/Subscription"; import type {Router} from "vue-router"; +import {SessionHolderImpl} from "@/ts/classes/SessionHolderImpl"; +import type {SessionHolder} from "@/ts/types/types"; const logger: Logger = loggerFactory.getLoggerColor("main", "#007a70"); @@ -52,8 +42,8 @@ logger.log(`Evaluating main script ${constants.GIT_HASH}`)(); // eslint-disable-next-line max-params function bootstrapVue( messageBus: Subscription, - api: Api, - ws: WsHandler, + api: HttpApi, + ws: WsApi, webrtcApi: WebRtcApi, platformUtil: PlatformUtil, messageSenderProxy: MessageSenderProxy, @@ -80,9 +70,6 @@ function bootstrapVue( no-underscore-dangle */ const message = `Error occurred in ${vm?.$options?.__file}:${err}:\n${info}`; - if (store?.userSettings?.sendLogs && api) { - void api.sendLogs(`${vm?.$options?.__file}:${err}:${info}`, browserVersion, constants.GIT_HASH); - } /* eslint-enable @typescript-eslint/restrict-template-expressions, @typescript-eslint/restrict-template-expressions, @@ -117,9 +104,9 @@ function init(): void { event.preventDefault(); }); + const sessionHolder: SessionHolder = new SessionHolderImpl(); const sub = new Subscription(); - const xhr: Http = /* Window.fetch ? new Fetch(XHR_API_URL, sessionHolder) :*/ new Xhr(sessionHolder); - const api: Api = new Api(xhr, sub); + const api: HttpApi = new HttpApi(sessionHolder); let storage; try { @@ -133,7 +120,7 @@ function init(): void { store.setStorage(storage); const smileyApi = new SmileysApi(store); void smileyApi.init(); - const ws: WsHandler = new WsHandler(WS_API_URL, sessionHolder, store, sub); + const ws: WsApi = new WsApi(WS_API_URL, sessionHolder, store, sub); const notifier: NotifierHandler = new NotifierHandler(api, browserVersion, isChrome, isMobile, ws, store, mainWindow, sub); const messageHelper: MessageHelper = new MessageHelper(store, notifier, sub, audioPlayer); const wsMessageHandler: WsMessageHandler = new WsMessageHandler(store, api, ws, messageHelper, sub); @@ -141,26 +128,18 @@ function init(): void { const webrtcApi: WebRtcApi = new WebRtcApi(ws, store, notifier, messageHelper, sub); const platformUtil: PlatformUtil = constants.IS_ANDROID ? new AndroidPlatformUtil() : new WebPlatformUtils(); const messageSenderProxy: MessageSenderProxy = new MessageSenderProxy(store, webrtcApi, wsMessageHandler); - const router = routerFactory(sub); + const router = routerFactory(sub, sessionHolder); const vue = bootstrapVue(sub, api, ws, webrtcApi, platformUtil, messageSenderProxy, smileyApi, router); vue.mount(document.body); window.onerror = function onerror(msg, url, linenumber, column, errorObj): boolean { const message = `Error occurred in ${url!}:${linenumber!}\n${JSON.stringify(msg)}`; - if (store?.userSettings?.sendLogs && api) { - void api.sendLogs( - `${url!}:${linenumber!}:${column ?? "?"}\n${JSON.stringify(msg)}\n\nOBJ: ${JSON.stringify(errorObj) ?? "?"}`, - browserVersion, - constants.GIT_HASH, - ); - } void store.growlError(message); return false; }; - window.GIT_VERSION = constants.GIT_HASH; if (constants.IS_DEBUG) { window.vue = vue; @@ -168,7 +147,6 @@ function init(): void { window.roomHandler = roomHandler; window.ws = ws; window.api = api; - window.xhr = xhr; window.storage = storage; window.webrtcApi = webrtcApi; window.sub = sub; diff --git a/frontend/src/ts/message_handlers/AbstractMessageProcessor.ts b/frontend/src/ts/message_handlers/AbstractMessageProcessor.ts index 52d5e22ff..5651660c0 100644 --- a/frontend/src/ts/message_handlers/AbstractMessageProcessor.ts +++ b/frontend/src/ts/message_handlers/AbstractMessageProcessor.ts @@ -1,15 +1,15 @@ -import type {Logger} from "lines-logger"; -import loggerFactory from "@/ts/instances/loggerFactory"; -import type {MessageSupplier} from "@/ts/types/types"; -import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type {DefaultWsOutMessage} from "@/ts/types/messages/wsOutMessages"; -import type {DefaultWsInMessage} from "@/ts/types/messages/wsInMessages"; import type { - DefaultMessage, + DefaultWsInMessage, + DefaultWsOutMessage, HandlerName, -} from "@/ts/types/messages/baseMessagesInterfaces"; + RequestWsOutMessage, + ResponseWsInMessage, +} from "@common/ws/common"; +import type {Logger} from "lines-logger"; +import loggerFactory from "@/ts/instances/loggerFactory"; + -export default class AbstractMessageProcessor { +export default abstract class AbstractMessageProcessor { protected readonly callBacks: Record = {}; protected readonly logger: Logger; @@ -21,32 +21,18 @@ export default class AbstractMessageProcessor { */ protected uniquePositiveMessageId: number = 0; - protected readonly target: MessageSupplier; - - protected readonly store: DefaultStore; - private readonly loggerIn: Logger; private readonly loggerOut: Logger; - public constructor(target: MessageSupplier, store: DefaultStore, label: string) { - this.target = target; - this.store = store; + public constructor(label: string) { this.loggerIn = loggerFactory.getLoggerColor(`${label}:in`, "#4c002b"); this.loggerOut = loggerFactory.getLoggerColor(`${label}:out`, "#4c002b"); this.logger = loggerFactory.getLoggerColor("mes-proc", "#4c002b"); } - public setMessageCbId, K extends string>(message: T) { - if (message.cbId) { - throw Error("Cb id should ge generated by this"); - } - // Should be ++, so uniqueId won't be 0, if it's 0 then checks like !0 are false, but should be true - message.cbId = ++this.uniquePositiveMessageId; - } - - public parseMessage(jsonData: string): DefaultWsInMessage | null { - let data: DefaultWsInMessage | null = null; + public parseMessage(jsonData: string): DefaultWsInMessage | ResponseWsInMessage | null { + let data: DefaultWsInMessage | null = null; try { data = JSON.parse(jsonData); this.logData(this.loggerIn, jsonData, data!)(); @@ -58,31 +44,33 @@ export default class AbstractMessageProcessor { return data; } - public async sendToServerAndAwait, K extends string>(message: T): Promise { + public async sendToServerAndAwait, RES extends ResponseWsInMessage>(message: Omit): Promise { return new Promise((resolve, reject) => { - this.setMessageCbId(message); + (message as any).cbId = ++this.uniquePositiveMessageId; const jsonMessage = this.getJsonMessage(message); - this.callBacks[message.cbId!] = { + this.callBacks[(message as any).cbId!] = { resolve, reject, }; - const isSent = this.target.sendRawTextToServer(jsonMessage); + const isSent = this.sendRawTextToServer(jsonMessage); if (isSent) { this.logData(this.loggerOut, jsonMessage, message)(); } }); } - sendToServer(message: DefaultWsOutMessage): boolean { + abstract sendRawTextToServer(data: string): boolean; + + sendToServer(message: DefaultWsOutMessage): boolean { const jsonMessage = this.getJsonMessage(message); - const isSent = this.target.sendRawTextToServer(jsonMessage); + const isSent = this.sendRawTextToServer(jsonMessage); if (isSent) { this.logData(this.loggerOut, jsonMessage, message)(); } return isSent; } - public getJsonMessage(message: DefaultWsOutMessage) { + public getJsonMessage(message: DefaultWsOutMessage) { return JSON.stringify(message); } @@ -100,7 +88,7 @@ export default class AbstractMessageProcessor { } } - private logData(logger: Logger, jsonData: string, message: DefaultMessage): () => void { + private logData(logger: Logger, jsonData: string, message: DefaultWsInMessage | DefaultWsOutMessage): () => void { let raw = jsonData; if (raw.length > 1000) { raw = ""; diff --git a/frontend/src/ts/message_handlers/Api.ts b/frontend/src/ts/message_handlers/Api.ts deleted file mode 100644 index 02975a441..000000000 --- a/frontend/src/ts/message_handlers/Api.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { - CONNECTION_ERROR, - GIPHY_API_KEY, - GIPHY_URL, - RESPONSE_SUCCESS, -} from "@/ts/utils/consts"; -import type {UploadFile} from "@/ts/types/types"; -import type { - OauthSessionResponse, - OauthStatus, - SaveFileResponse, - SessionResponse, - ViewUserProfileDto, -} from "@/ts/types/dto"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; -import loggerFactory from "@/ts/instances/loggerFactory"; -import type {Logger} from "lines-logger"; -import type Http from "@/ts/classes/Http"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {InternetAppearMessage} from "@/ts/types/messages/innerMessages"; -import type {MultiResponse} from "giphy-api"; -import type Subscription from "@/ts/classes/Subscription"; - -export default class Api extends MessageHandler { - protected readonly handlers: HandlerTypes = { - internetAppear: > this.internetAppear, - }; - - protected readonly logger: Logger; - - private readonly xhr: Http; - - private retryFcb: Function | null = null; - - public constructor(xhr: Http, sub: Subscription) { - super(); - sub.subscribe("*", this); - this.logger = loggerFactory.getLogger("api"); - this.xhr = xhr; - } - - public async login(form: HTMLFormElement): Promise { - return this.xhr.doPost({ - url: "/auth", - isJsonDecoded: true, - formData: new FormData(form), - }); - } - - public async sendLogs(issue: string, browser: string, version: string): Promise { - const result: string = await this.xhr.doPost({ - url: "/report_issue", - params: { - issue, - browser, - version, - }, - checkOkString: true, - }); - } - - public async changePassword(old_password: string, password: string): Promise { - return this.xhr.doPost({ - url: "/change_password", - params: { - old_password, - password, - }, - checkOkString: true, - }); - } - - public async logout(registration_id: string | null = null): Promise { - await this.xhr.doPost({ - url: "/logout", - params: {registration_id}, - errorDescription: "Error while logging out: ", - }); - } - - public async sendRestorePassword(form: HTMLFormElement): Promise { - return this.xhr.doPost({ - url: "/send_restore_password", - formData: new FormData(form), - checkOkString: true, - }); - } - - public async register(form: HTMLFormElement): Promise { - return this.xhr.doPost({ - url: "/register", - isJsonDecoded: true, - formData: new FormData(form), - }); - } - - public async registerDict(password: string, username: string): Promise { - return this.xhr.doPost({ - url: "/register", - isJsonDecoded: true, - params: { - username, - password, - }, - }); - } - - public async searchGiphys( - text: string, - offset: number, - limit: number, - process?: (R: XMLHttpRequest) => void, - ): Promise { - let response!: MultiResponse; - if ((/^\s*$/).exec(text)) { - response = await this.xhr.doGet({ - isJsonDecoded: true, - skipAuth: true, - // https://developers.giphy.com/docs/api/endpoint#trending - url: `/gifs/trending?api_key=${GIPHY_API_KEY}&limit=${limit}&offset=${offset}`, - baseUrl: GIPHY_URL, - process, - }); - } else { - response = await this.xhr.doGet({ - isJsonDecoded: true, - skipAuth: true, - // https://developers.giphy.com/docs/api/endpoint#search - url: `/gifs/search?api_key=${GIPHY_API_KEY}&limit=12&q=${encodeURIComponent(text)}&offset=${offset}`, - baseUrl: GIPHY_URL, - process, - }); - } - - if (response?.meta?.msg === "OK") { - return response; - } - throw Error(`Invalid giphy response ${response?.meta?.msg}`); - } - - public async getOauthStatus(): Promise { - return this.xhr.doGet({ - url: "/oauth_status", - isJsonDecoded: true, - }); - } - - public async setGoogleOauth(token: string): Promise { - return this.xhr.doPost({ - url: "/set_google_oauth", - checkOkString: true, - params: {token}, - }); - } - - public async setFacebookOauth(token: string): Promise { - return this.xhr.doPost({ - url: "/set_facebook_oauth", - checkOkString: true, - params: {token}, - }); - } - - public async googleAuth(token: string): Promise { - return this.xhr.doPost({ - url: "/google_auth", - isJsonDecoded: true, - params: { - token, - }, - }); - } - - public async facebookAuth(token: string): Promise { - return this.xhr.doPost({ - url: "/facebook_auth", - isJsonDecoded: true, - params: { - token, - }, - }); - } - - public async loadGoogle(): Promise { - await this.xhr.loadJs("https://apis.google.com/js/platform.js"); - } - - public async loadFacebook(): Promise { - await this.xhr.loadJs("https://connect.facebook.net/en_US/sdk.js"); - } - - public async loadRecaptcha(): Promise { - await this.xhr.loadJs("https://www.google.com/recaptcha/api.js"); - } - - public async registerFCB(registration_id: string, agent: string, is_mobile: boolean): Promise { - try { - await this.xhr.doPost({ - url: "/register_fcb", - params: { - registration_id, - agent, - is_mobile, - }, - isJsonEncoded: true, - checkOkString: true, - }); - return; - } catch (e) { - if (e === CONNECTION_ERROR) { - this.retryFcb = () => { - this.registerFCB(registration_id, agent, is_mobile); - }; - } else { - this.retryFcb = null; - } - throw e; - } - } - - public async validateUsername(username: string, process: (r: XMLHttpRequest) => void): Promise { - return this.xhr.doPost({ - url: "/validate_user", - params: {username}, - checkOkString: true, - process, - }); - } - - public async uploadProfileImage(file: Blob): Promise { - const fd = new FormData(); - fd.append("file", file); - - return this.xhr.doPost({ - url: "/upload_profile_image", - formData: fd, - checkOkString: true, - }); - } - - public async showProfile(id: number): Promise { - return this.xhr.doGet({ - url: `/profile?id=${id}`, - isJsonDecoded: true, - }); - } - - public async changeEmail(token: string): Promise { - return this.xhr.doGet({ - url: `/change_email?token=${token}`, - }); - } - - public async changeEmailLogin(email: string, password: string): Promise { - return this.xhr.doPost({ - url: "/change_email_login", - checkOkString: true, - params: { - email, - password, - }, - }); - } - - public async confirmEmail(token: string): Promise { - return this.xhr.doGet({ - url: `/confirm_email?token=${token}`, - checkOkString: true, - }); - } - - public async uploadFiles(files: UploadFile[], progress: (e: ProgressEvent) => void, setXhr: (e: XMLHttpRequest) => void): Promise { - const fd = new FormData(); - files.forEach((sd) => { - fd.append(sd.key, sd.file, sd.file.name); - }); - - return this.xhr.doPost({ - url: "/upload_file", - isJsonDecoded: true, - formData: fd, - process: (r) => { - setXhr(r); - r.upload.addEventListener("progress", progress); - }, - }); - } - - public async validateEmail(email: string, process: (r: XMLHttpRequest) => void): Promise { - return this.xhr.doPost({ - url: "/validate_email", - params: {email}, - checkOkString: true, - process, - }); - } - - public async verifyToken(token: string): Promise { - const value: {message: string; restoreUser: string} = await this.xhr.doPost<{message: string; restoreUser: string}>({ - url: "/verify_token", - isJsonDecoded: true, - params: {token}, - }); - if (value && value.message === RESPONSE_SUCCESS) { - return value.restoreUser; - } - throw value.message; - } - - public async acceptToken(token: string, password: string): Promise { - return this.xhr.doPost({ - url: "/accept_token", - params: { - token, - password, - }, - checkOkString: true, - }); - } - - public internetAppear(m: InternetAppearMessage): void { - if (this.retryFcb) { - this.retryFcb(); - } - } -} diff --git a/frontend/src/ts/message_handlers/HttpApi.ts b/frontend/src/ts/message_handlers/HttpApi.ts new file mode 100644 index 000000000..01ea94772 --- /dev/null +++ b/frontend/src/ts/message_handlers/HttpApi.ts @@ -0,0 +1,36 @@ +import Fetch from "@/ts/classes/Fetch"; +import AuthApi from "@/ts/message_handlers/http_modules/AuthApi"; +import type {SessionHolder} from "@/ts/types/types"; +import {XHR_API_URL} from "@/ts/utils/runtimeConsts"; +import VerifyApi from "@/ts/message_handlers/http_modules/VerifyApi"; +import FileApi from "@/ts/message_handlers/http_modules/FileApi"; +import RestApi from "@/ts/message_handlers/http_modules/RestApi"; +import JsApi from "@/ts/message_handlers/http_modules/JsApi"; +import GiphyApi from "@/ts/message_handlers/http_modules/GiphyApi"; +import {GIPHY_API_KEY, GIPHY_URL} from "@/ts/utils/consts"; + +export default class HttpApi { + public readonly authApi: AuthApi; + + public readonly verifyApi: VerifyApi; + + public readonly fileApi: FileApi; + + public readonly restApi: RestApi; + + public readonly jsApi: JsApi; + + public readonly giphyApi: GiphyApi; + + public constructor(sessionHolder: SessionHolder) { + const getHeaders = (): Record => ({ + "session-id": sessionHolder.session!, + }); + this.authApi = new AuthApi(new Fetch(`${XHR_API_URL}/auth`, getHeaders)); + this.verifyApi = new VerifyApi(new Fetch(`${XHR_API_URL}/verify`, getHeaders)); + this.fileApi = new FileApi(new Fetch(`${XHR_API_URL}/file`, getHeaders)); + this.restApi = new RestApi(new Fetch(XHR_API_URL, getHeaders)); + this.giphyApi = new GiphyApi(new Fetch(`${GIPHY_URL}/gifs`, () => ({})), GIPHY_API_KEY); + this.jsApi = new JsApi(); + } +} diff --git a/frontend/src/ts/message_handlers/MesageHandler.ts b/frontend/src/ts/message_handlers/MesageHandler.ts deleted file mode 100644 index f78b69018..000000000 --- a/frontend/src/ts/message_handlers/MesageHandler.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type {Logger} from "lines-logger"; -import type { - DefaultInMessage, - HandlerName, - HandlerType, - HandlerTypes, - IMessageHandler, -} from "@/ts/types/messages/baseMessagesInterfaces"; - - -export default abstract class MessageHandler implements IMessageHandler { - protected abstract readonly logger: Logger; - - protected abstract readonly handlers: HandlerTypes; - - public handle(message: DefaultInMessage) { - if (!this.handlers) { - throw Error(`${this.constructor.name} has empty handlers`); - } - const handler: HandlerType | undefined = this.handlers[message.action]; - if (handler) { - handler.bind(this)(message); - this.logger.debug("Notified {}.{} => message", this.constructor.name, message.action, message); - } else { - this.logger.error("{} can't find handler for {}, available handlers {}. Message: {}", this.constructor.name, message.action, Object.keys(this.handlers), message)(); - } - } - - getHandler(message: DefaultInMessage): HandlerType | undefined { - return this.handlers[message.action]; - } -} diff --git a/frontend/src/ts/message_handlers/MessageHelper.ts b/frontend/src/ts/message_handlers/MessageHelper.ts index 9ec9aafef..01d7bb6b7 100644 --- a/frontend/src/ts/message_handlers/MessageHelper.ts +++ b/frontend/src/ts/message_handlers/MessageHelper.ts @@ -1,9 +1,6 @@ import faviconUrl from "@/assets/img/favicon.ico"; import {incoming} from "@/ts/utils/audio"; -import type { - FileModel, - MessageModel, -} from "@/ts/types/model"; +import type {FileModel, MessageModel} from "@/ts/types/model"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; import type NotifierHandler from "@/ts/classes/NotificationHandler"; import type {AudioPlayer} from "@/ts/classes/AudioPlayer"; @@ -11,6 +8,7 @@ import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; import {resolveMediaUrl} from "@/ts/utils/htmlApi"; import type Subscription from "@/ts/classes/Subscription"; +import type {ScrollInnerSystemMessage} from "@/ts/types/messages/inner/scroll"; export class MessageHelper { private readonly logger: Logger; @@ -40,9 +38,10 @@ export class MessageHelper { } public processAnyMessage() { - this.messageBus.notify({ + this.messageBus.notify({ action: "scroll", handler: "*", + data: null, }); } @@ -51,9 +50,9 @@ export class MessageHelper { const room = this.store.roomsDict[message.roomId]; if (room.notifications) { - const title = this.store.allUsersDict[message.userId].user; + const title = this.store.allUsersDict[message.userId].username; - let icon: string = resolveMediaUrl(this.store.allUsersDict[message.userId].image) || faviconUrl; + let icon: string = resolveMediaUrl(this.store.allUsersDict[message.userId].thumbnail) || faviconUrl; if (message.files) { const fff: FileModel = Object.values(message.files)[0]; if (fff?.url) { diff --git a/frontend/src/ts/message_handlers/P2PMessageProcessor.ts b/frontend/src/ts/message_handlers/P2PMessageProcessor.ts deleted file mode 100644 index 5e887c989..000000000 --- a/frontend/src/ts/message_handlers/P2PMessageProcessor.ts +++ /dev/null @@ -1,14 +0,0 @@ -import AbstractMessageProcessor from "@/ts/message_handlers/AbstractMessageProcessor"; -import type {DefaultP2pMessage} from "@/ts/types/messages/p2pMessages"; - -export class P2PMessageProcessor extends AbstractMessageProcessor { - public resolveCBifItsThere(data: DefaultP2pMessage): boolean { - this.logger.debug("resolving cb")(); - if (data.resolveCbId) { - this.callBacks[data.resolveCbId].resolve(data); - delete this.callBacks[data.resolveCbId]; - return true; - } - return false; - } -} diff --git a/frontend/src/ts/message_handlers/RomHandler.ts b/frontend/src/ts/message_handlers/RomHandler.ts index afc53aa98..30c1ddd4b 100644 --- a/frontend/src/ts/message_handlers/RomHandler.ts +++ b/frontend/src/ts/message_handlers/RomHandler.ts @@ -1,21 +1,59 @@ -import MessageHandler from "@/ts/message_handlers/MesageHandler"; +import type {ChannelDto} from "@common/model/dto/channel.dto"; +import type {RoomDto} from "@common/model/dto/room.dto"; +import type {AddRoomBase, ChangeDeviceType} from "@common/model/ws.base"; +import {AddChannelWsInBody} from "@common/ws/message/room/add.channel"; import type { - RoomLogEntry, - SetRoomsUsers, -} from "@/ts/types/types"; + AddChannelWsInMessage, +} from "@common/ws/message/room/add.channel"; +import {AddInviteWsInBody} from "@common/ws/message/room/add.invite"; import type { - AddRoomBase, - ChangeDeviceType, - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; + AddInviteWsInMessage, +} from "@common/ws/message/room/add.invite"; +import {AddOnlineUserWsInBody} from "@common/ws/message/room/add.online.user"; import type { - ChangeP2pRoomInfoMessage, - ChangeUserOnlineInfoMessage, - LogoutMessage, - PubSetRooms, - RouterNavigateMessage, -} from "@/ts/types/messages/innerMessages"; + AddOnlineUserWsInMessage, +} from "@common/ws/message/room/add.online.user"; +import {AddRoomWsInBody} from "@common/ws/message/room/add.room"; +import type { + AddRoomWsInMessage, +} from "@common/ws/message/room/add.room"; +import {DeleteChannelWsInBody} from "@common/ws/message/room/delete.channel"; +import type { + DeleteChannelWsInMessage, +} from "@common/ws/message/room/delete.channel"; +import {DeleteRoomWsInBody} from "@common/ws/message/room/delete.room"; +import type { + DeleteRoomWsInMessage, +} from "@common/ws/message/room/delete.room"; +import {InviteUserWsInBody} from "@common/ws/message/room/invite.user"; +import type { + InviteUserWsInMessage, +} from "@common/ws/message/room/invite.user"; +import {LeaveUserWsInBody} from "@common/ws/message/room/leave.user"; +import type { + LeaveUserWsInMessage, +} from "@common/ws/message/room/leave.user"; +import {RemoveOnlineUserWsInBody} from "@common/ws/message/room/remove.online.user"; +import type { + RemoveOnlineUserWsInMessage, +} from "@common/ws/message/room/remove.online.user"; +import {SaveChannelSettingsWsInBody} from "@common/ws/message/room/save.channel.settings"; +import type { + SaveChannelSettingsWsInMessage, +} from "@common/ws/message/room/save.channel.settings"; +import type { + SaveRoomSettingsWsInBody, + SaveRoomSettingsWsInMessage +} from "@common/ws/message/room/save.room.settings"; + +import type {ChangeP2pRoomInfoMessage} from "@/ts/types/messages/inner/change.p2p.room.info"; +import type {ChangeOnlineMessage} from "@/ts/types/messages/inner/change.user.online.info"; +import type {LogoutMessage} from "@/ts/types/messages/inner/logout"; +import type {PubSetRoomsMessage} from "@/ts/types/messages/inner/pub.set.rooms"; +import type {RouterNavigateMessage} from "@/ts/types/messages/inner/router.navigate"; + +import type {RoomLogEntry, SetRoomsUsers} from "@/ts/types/types"; + import type { ChannelModel, ChannelsDictModel, @@ -25,69 +63,38 @@ import type { UserDictModel, UserModel, } from "@/ts/types/model"; -import { - convertUser, - getChannelDict, - getRoom, - getRoomsBaseDict, -} from "@/ts/types/converters"; -import type { - ChannelDto, - RoomDto, - SetStateFromWS, -} from "@/ts/types/dto"; -import type { - AddChannelMessage, - AddInviteMessage, - AddOnlineUserMessage, - AddRoomMessage, - CreateNewUsedMessage, - DeleteChannelMessage, - DeleteRoomMessage, - InviteUserMessage, - LeaveUserMessage, - RemoveOnlineUserMessage, - SaveChannelSettingsMessage, - SaveRoomSettingsMessage, - ShowITypeMessage, -} from "@/ts/types/messages/wsInMessages"; +import {convertUser, getChannelDict, getRoom, getRoomsBaseDict} from "@/ts/types/converters"; + import {ALL_ROOM_ID} from "@/ts/utils/consts"; import type {Logger} from "lines-logger"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type Api from "@/ts/message_handlers/Api"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type HttpApi from "@/ts/message_handlers/HttpApi"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type {AudioPlayer} from "@/ts/classes/AudioPlayer"; import loggerFactory from "@/ts/instances/loggerFactory"; -import { - login, - logout, -} from "@/ts/utils/audio"; +import {login, logout} from "@/ts/utils/audio"; import type Subscription from "@/ts/classes/Subscription"; -export class RoomHandler extends MessageHandler { - protected readonly logger: Logger; +import type {SetStateFromWS} from "@/ts/types/dto"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {InternetAppearMessage} from "@/ts/types/messages/inner/internet.appear"; +import {PubSetRoomsMessageBody} from "@/ts/types/messages/inner/pub.set.rooms"; +import { + ShowITypeWsInBody, + ShowITypeWsInMessage +} from "@common/ws/message/room/show.i.type"; +import { + CreateNewUserWsInBody, + CreateNewUserWsInMessage +} from "@common/ws/message/room/create.new.user"; + - protected readonly handlers: HandlerTypes = { - deleteRoom: > this.deleteRoom, - init: > this.init, - leaveUser: > this.leaveUser, - addRoom: > this.addRoom, - removeOnlineUser: > this.removeOnlineUser, - addChannel: > this.addChannel, - inviteUser: > this.inviteUser, - addInvite: > this.addInvite, - addOnlineUser: > this.addOnlineUser, - saveChannelSettings: > this.saveChannelSettings, - deleteChannel: > this.deleteChannel, - saveRoomSettings: > this.saveRoomSettings, - createNewUser: > this.createNewUser, - showIType: > this.showIType, - logout: > this.logout, - }; +export class RoomHandler { + protected readonly logger: Logger; private readonly store: DefaultStore; - private readonly ws: WsHandler; + private readonly ws: WsApi; private readonly audioPlayer: AudioPlayer; @@ -95,12 +102,11 @@ export class RoomHandler extends MessageHandler { public constructor( store: DefaultStore, - api: Api, - ws: WsHandler, + api: HttpApi, + ws: WsApi, audioPlayer: AudioPlayer, sub: Subscription, ) { - super(); this.store = store; this.sub = sub; sub.subscribe("room", this); @@ -109,7 +115,8 @@ export class RoomHandler extends MessageHandler { this.audioPlayer = audioPlayer; } - public leaveUser(message: LeaveUserMessage) { + @Subscribe() + public leaveUser(message: LeaveUserWsInBody) { if (this.store.roomsDict[message.roomId]) { const m: SetRoomsUsers = { roomId: message.roomId, @@ -130,22 +137,26 @@ export class RoomHandler extends MessageHandler { } } - public addRoom(message: AddRoomMessage) { + @Subscribe() + public addRoom(message: AddRoomWsInBody) { this.mutateRoomAddition(message, "room_created"); } - public removeOnlineUser(message: RemoveOnlineUserMessage) { - if (message.content[message.userId].length === 0) { + @Subscribe() + public removeOnlineUser(message: RemoveOnlineUserWsInBody) { + if (message.online[message.userId].length === 0) { this.addChangeOnlineEntry(message.userId, message.time, "gone offline"); } - this.store.setOnline(message.content); + this.store.setOnline(message.online); } - public addChannel(message: AddChannelMessage) { + @Subscribe() + public addChannel(message: AddChannelWsInBody) { this.mutateRoomAddition(message, "room_created"); } - public inviteUser(message: InviteUserMessage) { + @Subscribe() + public inviteUser(message: InviteUserWsInBody) { this.store.setRoomsUsers({ roomId: message.roomId, users: message.users, @@ -163,11 +174,13 @@ export class RoomHandler extends MessageHandler { this.notifyDevicesChanged(null, message.roomId, "someone_joined"); } - public addInvite(message: AddInviteMessage) { + @Subscribe() + public addInvite(message: AddInviteWsInBody) { this.mutateRoomAddition(message, "invited"); } - public createNewUser(message: CreateNewUsedMessage) { + @Subscribe() + public createNewUser(message: CreateNewUserWsInBody) { const newVar: UserModel = convertUser(message, null); this.store.addUser(newVar); message.rooms.forEach(({roomId, users}) => { @@ -181,20 +194,21 @@ export class RoomHandler extends MessageHandler { roomLog: { action: "joined this room", time: Date.now(), - userId: message.userId, + userId: message.id, }, }); this.notifyDevicesChanged(null, roomId, "someone_joined"); }); } - public addOnlineUser(message: AddOnlineUserMessage) { - if (message.content[message.userId].length === 1) { + @Subscribe() + public addOnlineUser(message: AddOnlineUserWsInBody) { + if (message.online[message.userId].length === 1) { // Exactly 1 device is now offline, so that new that appeared is the first one this.addChangeOnlineEntry(message.userId, message.time, "appeared online"); } - this.store.setOnline({...message.content}); // Prevent modifying original object - const payload: ChangeUserOnlineInfoMessage = { + this.store.setOnline({...message.online}); // Prevent modifying original object + const payload: ChangeOnlineMessage = { handler: "webrtc", allowZeroSubscribers: true, action: "changeOnline", @@ -205,9 +219,10 @@ export class RoomHandler extends MessageHandler { this.sub.notify(payload); } - public saveChannelSettings(message: SaveChannelSettingsMessage) { - if (!this.store.channelsDict[message.channelId]) { - this.logger.error("Unable to find channel to edit {} to kick user, available are {}", message.channelId, Object.keys(this.store.channelsDict))(); + @Subscribe() + public saveChannelSettings(message: SaveChannelSettingsWsInBody) { + if (!this.store.channelsDict[message.id]) { + this.logger.error("Unable to find channel to edit {} to kick user, available are {}", message.id, Object.keys(this.store.channelsDict))(); } else { const c: ChannelModel = getChannelDict(message); this.store.addChannel(c); @@ -220,14 +235,16 @@ export class RoomHandler extends MessageHandler { } } - public deleteChannel(message: DeleteChannelMessage) { + @Subscribe() + public deleteChannel(message: DeleteChannelWsInBody) { this.store.deleteChannel(message.channelId); message.roomIds.forEach((id) => { this.doRoomDelete(id); }); } - public async showIType(message: ShowITypeMessage) { + @Subscribe() + public async showIType(message: ShowITypeWsInBody) { if (this.store.myId !== message.userId) { await this.store.showUserIsTyping({ userId: message.userId, @@ -236,50 +253,53 @@ export class RoomHandler extends MessageHandler { } } - public saveRoomSettings(message: SaveRoomSettingsMessage) { - const oldRoom = this.store.roomsDict[message.roomId]; + @Subscribe() + public saveRoomSettings(message: SaveRoomSettingsWsInBody) { + const oldRoom = this.store.roomsDict[message.id]; if (!oldRoom) { - this.logger.error("Unable to find channel to edit {} to kick user, available are {}", message.roomId, Object.keys(this.store.roomsDict))(); + this.logger.error("Unable to find channel to edit {} to kick user, available are {}", message.id, Object.keys(this.store.roomsDict))(); } else { const r: RoomSettingsModel = getRoom(message); const oldRoomP2p: boolean = oldRoom.p2p; this.store.setRoomSettings(r); if (oldRoomP2p !== message.p2p) { - this.notifyDevicesChanged(null, message.roomId, message.p2p ? "room_created" : "i_deleted"); + this.notifyDevicesChanged(null, message.id, message.p2p ? "room_created" : "i_deleted"); } } } - - public deleteRoom(message: DeleteRoomMessage) { + @Subscribe() + public deleteRoom(message: DeleteRoomWsInBody) { this.doRoomDelete(message.roomId); } - public logout(m: LogoutMessage) { + @Subscribe() + public logout() { this.store.logout(); } - public init(m: PubSetRooms) { + @Subscribe() + public init(m: PubSetRoomsMessageBody) { const {rooms, channels, users, online} = m; /* * Otherwise, we will modify value from ws, which will make observable in logs * other values from 'm' are converted with convertable */ - const ids: PubSetRooms["online"] = JSON.parse(JSON.stringify(online)); + const ids: PubSetRoomsMessage["online"] = JSON.parse(JSON.stringify(online)); this.store.setOnline(ids); this.logger.debug("set users {}", users)(); const um: UserDictModel = {}; users.forEach((u) => { - um[u.userId] = convertUser(u, null); + um[u.id] = convertUser(u, null); }); this.logger.debug("Setting rooms")(); const storeRooms: RoomDictModel = {}; const {roomsDict} = this.store; rooms.forEach((newRoom: RoomDto) => { - const oldRoom = roomsDict[newRoom.roomId]; + const oldRoom = roomsDict[newRoom.id]; const rm: RoomModel = getRoomsBaseDict(newRoom, oldRoom); storeRooms[rm.id] = rm; }); @@ -287,7 +307,7 @@ export class RoomHandler extends MessageHandler { this.logger.debug("Setting channels")(); const {channelsDict} = this.store; const storeChannel: ChannelsDictModel = channels.reduce((dict: ChannelsDictModel, newChannel: ChannelDto) => { - const oldChannel = channelsDict[newChannel.channelId]; + const oldChannel = channelsDict[newChannel.id]; const cm: ChannelModel = getChannelDict(newChannel, oldChannel); dict[cm.id] = cm; return dict; @@ -355,7 +375,7 @@ export class RoomHandler extends MessageHandler { private mutateRoomAddition(message: AddRoomBase, type: "invited" | "room_created") { if (message.channelId) { - // As (Omit & {action: 'addChannel'}) + // As (Omit & {action: 'addChannel'}) const channelDict: ChannelModel = getChannelDict(message as any); this.store.addChannel(channelDict); } @@ -370,6 +390,6 @@ export class RoomHandler extends MessageHandler { }, }); // Eiher I created this room, either I was invited to this room - this.notifyDevicesChanged(null, message.roomId, type); // TODO messageTransferhandler should be created or should id? + this.notifyDevicesChanged(null, message.id, type); // TODO messageTransferhandler should be created or should id? } } diff --git a/frontend/src/ts/message_handlers/WsApi.ts b/frontend/src/ts/message_handlers/WsApi.ts new file mode 100644 index 000000000..1b82ee609 --- /dev/null +++ b/frontend/src/ts/message_handlers/WsApi.ts @@ -0,0 +1,613 @@ +import type {GiphyDto} from "@common/model/dto/giphy.dto"; +import type {RoomNoUsersDto} from "@common/model/dto/room.dto"; +import type { + UserProfileDto, + UserProfileDtoWoImage, +} from "@common/model/dto/user.profile.dto"; +import type {UserSettingsDto} from "@common/model/dto/user.settings.dto"; +import type {MessageStatus} from "@common/model/enum/message.status"; +import type {MessagesResponseMessage} from "@common/model/ws.base"; +import type {AddChannelWsInMessage} from "@common/ws/message/room/add.channel"; +import type {AddInviteWsInMessage} from "@common/ws/message/room/add.invite"; +import type {AddRoomWsInMessage, AddRoomWsInBody} from "@common/ws/message/room/add.room"; +import type {SaveChannelSettingsWsInMessage} from "@common/ws/message/room/save.channel.settings"; +import type {ShowITypeWsOutMessage} from "@common/ws/message/room/show.i.type"; + +import type {PingWsInMessage} from "@common/ws/message/ws/ping"; +import {PingWsInBody} from "@common/ws/message/ws/ping"; +import type {SetProfileImageWsInMessage} from "@common/ws/message/ws/set.profile.image"; +import {SetProfileImageWsInBody} from "@common/ws/message/ws/set.profile.image"; +import type { + SetSettingsMessage, + SetSettingsWsOutMessage, +} from "@common/ws/message/ws/set.settings"; +import {SetSettingBody} from "@common/ws/message/ws/set.settings"; +import type { + SetUserProfileMessage, + SetUserProfileWsOutMessage, +} from "@common/ws/message/ws/set.user.profile"; +import {SetUserProfileBody} from "@common/ws/message/ws/set.user.profile"; +import type {SetWsIdWsInMessage} from "@common/ws/message/ws/set.ws.id"; +import {SetWsIdBody} from "@common/ws/message/ws/set.ws.id"; +import type {UserProfileChangedWsInMessage} from "@common/ws/message/ws/user.profile.changed"; +import {UserProfileChangedWsInBody} from "@common/ws/message/ws/user.profile.changed"; +import type { + GetCountryCodeWsInBody, + GetCountryCodeWsInMessage, + GetCountryCodeWsOutMessage, +} from "@common/ws/message/get.country.code"; +import type {SetMessageStatusWsOutMessage} from "@common/ws/message/set.message.status"; +import type { + SyncHistoryWsInMessage, + SyncHistoryWsOutMessage, +} from "@common/ws/message/sync.history"; +import type {DefaultWsOutMessage} from "@common/ws/common"; +import {WS_SESSION_EXPIRED_CODE} from "@common/consts"; +import type {InternetAppearMessage} from "@/ts/types/messages/inner/internet.appear"; +import type {LogoutMessage} from "@/ts/types/messages/inner/logout"; +import type {PubSetRoomsMessage} from "@/ts/types/messages/inner/pub.set.rooms"; + +import { + CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT, + CONNECTION_RETRY_TIME, + FLAGS, + IS_DEBUG, + LOG_LEVEL_LS, +} from "@/ts/utils/consts"; +import type { + Logger, + LogLevel, +} from "lines-logger"; +import loggerFactory from "@/ts/instances/loggerFactory"; +import type { + CurrentUserInfoWoImage, + CurrentUserSettingsModel, + Location, +} from "@/ts/types/model"; +import type { + MessageSupplier, + SessionHolder, +} from "@/ts/types/types"; +import { + convertLocation, + currentUserInfoDtoToModel, + userSettingsDtoToModel, +} from "@/ts/types/converters"; + +import type {DefaultStore} from "@/ts/classes/DefaultStore"; +import {WsMessageProcessor} from "@/ts/message_handlers/WsMessageProcessor"; + + +import type Subscription from "@/ts/classes/Subscription"; +import {Subscribe} from "@/ts/utils/pubsub"; +import type { + PongWsInMessage, + PongWsOutMessage, +} from "@common/ws/message/ws/pong"; +import type { + OfferFileRequest, + OfferFileResponse, +} from "@common/ws/message/webrtc/offer.file"; +import type { + OfferCallRequestWsOutMessage, + OfferCallResponseWsInMessage, +} from "@common/ws/message/webrtc/offer.call"; +import type {AcceptFileWsOutMessage} from "@common/ws/message/webrtc-transfer/accept.file"; +import type {DestroyCallConnectionWsOutMessage} from "@common/ws/message/peer-connection/destroy.call.connection"; +import {WsState} from "@/ts/types/model"; +import type {WebRtcSetConnectionIdBody} from "@common/model/webrtc.base"; +import type {PrintMessageWsInMessage, + PrintMessageWsOutMessage, + PrintMessageWsOutBody} from "@common/ws/message/ws-message/print.message"; +import {AddRoomWsOutBody} from "@common/ws/message/room/add.room"; +import AbstractMessageProcessor from "@/ts/message_handlers/AbstractMessageProcessor"; + + +export default class WsApi implements MessageSupplier { + protected readonly logger: Logger; + + /* + * How much current time is ahead of the server time + * if current time is in the past it will be negative + */ + private timeDiffWithServer: number = 0; + + private pingTimeoutFunction: number | null = null; + + private readonly store: DefaultStore; + + private readonly sessionHolder: SessionHolder; + + private readonly wsMessageProcessor: WsMessageProcessor; + + private readonly sub: Subscription; + + public constructor(API_URL: string, sessionHolder: SessionHolder, store: DefaultStore, sub: Subscription) { + this.sub = sub; + this.sub.subscribe("ws", this); + this.wsMessageProcessor = new WsMessageProcessor(API_URL, store, "ws", sub); + this.logger = loggerFactory.getLoggerColor("ws", "#4c002b"); + this.sessionHolder = sessionHolder; + this.store = store; + } + + + public async getCountryCode(): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "getCountryCode", + data: null, + }); + } + + public async offerFile(roomId: number, browser: string, name: string, size: number, threadId: number | null): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "offerFile", + data: { + roomId, + threadId, + name, + size, + browser, + }, + }); + } + + public async offerCall(roomId: number, browser: string): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "offerCall", + data: { + roomId, + browser, + }, + }); + } + + public acceptFile(connId: string, received: number) { + this.sendToServer({ + action: "acceptFile", + data: { + connId, + received, + }, + }); + } + + public replyFile(connId: string, browser: string) { + this.sendToServer({ + action: "replyFile", + connId, + content: {browser}, + }); + } + + public destroyFileConnection(connId: string, content: unknown) { + this.sendToServer({ + content, + action: "destroyFileConnection", + connId, + }); + } + + public destroyPeerFileConnection(connId: string, content: any, opponentWsId: string) { + this.sendToServer({ + content, + opponentWsId, + action: "destroyFileConnection", + connId, + }); + } + + public async search( + searchString: string, + roomId: number, + offset: number, + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + searchString, + roomId, + offset, + action: "searchMessages", + }); + } + + public sendEditMessage( + content: string | null, + id: number, + files: number[] | null, + tags: Record, + giphies: GiphyDto[], + ) { + const newVar = { + id, + action: "editMessage", + files, + tags, + giphies, + content, + }; + this.sendToServer(newVar, true); + } + + public async sendPrintMessage( + data: PrintMessageWsOutBody, + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "printMessage", + data, + }); + } + + public async saveSettings(content: UserSettingsDto): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "setSettings", + data: content, + }); + } + + public showIType(roomId: number): void { + this.sendToServer({ + action: "showIType", + data: { + roomId, + }, + }); + } + + public async saveUser(data: UserProfileDtoWoImage): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "setUserProfile", + data, + }); + } + + public async sendAddRoom(data: AddRoomWsOutBody): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + data, + action: "addRoom", + }); + } + + public async syncHistory( + roomIds: number[], + messagesIds: number[], + receivedMessageIds: number[], + onServerMessageIds: number[], + lastSynced: number, + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + messagesIds, + receivedMessageIds, + onServerMessageIds, + roomIds, + lastSynced, + action: "syncHistory", + }); + } + + public async sendAddChannel(channelName: string, users: number[]): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + channelName, + users, + action: "addChannel", + }); + } + + public async sendRoomSettings(message: RoomNoUsersDto): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + ...message, + action: "saveRoomSettings", + }); + } + + public async sendDeleteChannel(channelId: number): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + channelId, + action: "deleteChannel", + }); + } + + public async sendLeaveChannel(channelId: number): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + channelId, + action: "leaveChannel", + }); + } + + public async saveChannelSettings( + channelName: string, + channelId: number, + channelCreatorId: number, + volume: number, + notifications: boolean, + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "saveChannelSettings", + channelId, + channelCreatorId, + channelName, + volume, + notifications, + }); + } + + public async inviteUser(roomId: number, users: number[]): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + roomId, + users, + action: "inviteUser", + }); + } + + + public pingServer() { + this.sendToServer({action: "ping"}, true); + } + + public async sendDeleteRoom(roomId: number) { + return this.wsMessageProcessor.sendToServerAndAwait({ + roomId, + action: "deleteRoom", + }); + } + + public async sendLeaveRoom(roomId: number) { + return this.wsMessageProcessor.sendToServerAndAwait({ + roomId, + action: "leaveUser", + }); + } + + public async setMessageStatus( + messagesIds: number[], + roomId: number, + status: MessageStatus, + ) { + return this.wsMessageProcessor.sendToServerAndAwait({ + messagesIds, + action: "setMessageStatus", + status, + roomId, // This room event will be published to + }); + } + + public async sendLoadMessages( + roomId: number, + count: number, + threadId: number | null, + excludeIds: number[], + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + count, + excludeIds, + threadId, + action: "loadMessages", + roomId, + }); + } + + public async sendLoadMessagesByIds( + roomId: number, + messagesIds: number[], + ): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + messagesIds, + action: "loadMessagesByIds", + roomId, + }); + } + + + + public sendRtcData(content: RTCIceCandidate | RTCSessionDescriptionInit, connId: string, opponentWsId: string) { + this.sendToServer({ + content, + connId, + opponentWsId, + action: "sendRtcData", + }); + } + + public retry(connId: string, opponentWsId: string) { + this.sendToServer({ + action: "retryFile", + connId, + opponentWsId, + }); + } + + public replyCall(connId: string, browser: string) { + this.sendToServer({ + action: "replyCall", + connId, + content: { + browser, + }, + }); + } + + public destroyCallConnection(connId: string, status: "decline" | "hangup") { + this.sendToServer({ + action: "destroyCallConnection", + data: { + status, + connId, + }, + }); + } + + public async offerMessageConnection(roomId: number): Promise { + return this.wsMessageProcessor.sendToServerAndAwait({ + action: "offerMessage", + roomId, + }); + } + + public acceptCall(connId: string) { + this.sendToServer({ + action: "acceptCall", + connId, + }); + } + + public joinCall(connId: string) { + this.sendToServer({ + action: "joinCall", + connId, + }); + } + + @Subscribe() + public setSettings(settings: SetSettingBody) { + const a: CurrentUserSettingsModel = userSettingsDtoToModel(settings); + this.setUserSettings(a); + } + + + @Subscribe() + public setUserProfile(m: SetUserProfileBody) { + const a: CurrentUserInfoWoImage = currentUserInfoDtoToModel(m); + a.id = this.store.userInfo!.id; // This could came only when we logged in + this.store.setUserInfo(a); + } + + @Subscribe() + public setProfileImage(m: SetProfileImageWsInBody) { + this.setUserImage(m.url); + } + + public convertServerTimeToPC(serverTime: number) { + return serverTime + this.timeDiffWithServer; // ServerTime + (Date.now - serverTime) === Date.now + } + + + @Subscribe() + public async setWsId(message: SetWsIdBody) { + this.wsMessageProcessor.setWsConnectionId(message.opponentWsId); + this.setUserInfo(message.profile); + this.setUserSettings(message.settings); + this.setUserImage(message.profile.thumbnail); + this.timeDiffWithServer = Date.now() - message.time; + this.sub.notify({ + action: "init", + data: { + channels: message.channels, + rooms: message.rooms, + online: message.online, + users: message.users, + }, + handler: "room", + }); + + this.sub.notify({ + action: "internetAppear", + handler: "*", + data: null, + }); + this.logger.debug("CONNECTION ID HAS BEEN SET TO {})", this.wsConnectionId)(); + if (FLAGS) { + const getCountryCodeMessage = await this.getCountryCode(); + const modelLocal: Record = {}; + Object.entries(getCountryCodeMessage.content).forEach(([k, v]) => { + modelLocal[k] = convertLocation(v); + }); + this.store.setCountryCode(modelLocal); + } + } + + @Subscribe() + public userProfileChanged(message: UserProfileChangedWsInBody) { + this.store.setUser(message); + } + + + @Subscribe() + public ping(message: PingWsInBody) { + this.wsMessageProcessor.startNoPingTimeout(); + this.sendToServer({ + action: "pong", + data: { + time: message.time, + }, + }); + } + + @Subscribe() + public pong() { + if (this.pingTimeoutFunction) { + this.logger.debug("Clearing pingTimeoutFunction")(); + clearTimeout(this.pingTimeoutFunction); + this.pingTimeoutFunction = null; + } + } + + notifyCallActive(param: {connectionId: string | null; opponentWsId: string; roomId: number}) { + this.sendToServer({ + action: "notifyCallActive", + connId: param.connectionId, + opponentWsId: param.opponentWsId, + roomId: param.roomId, + }); + } + + private setUserInfo(userInfo: UserProfileDto) { + const um: CurrentUserInfoWoImage = currentUserInfoDtoToModel(userInfo); + this.store.setUserInfo(um); + } + + private setUserSettings(userInfo: UserSettingsDto) { + const um: UserSettingsDto = userSettingsDtoToModel(userInfo); + const logLevel: LogLevel = userInfo.logs || (IS_DEBUG ? "trace" : "error"); + localStorage.setItem(LOG_LEVEL_LS, logLevel); + loggerFactory.setLogWarnings(logLevel); + this.store.setUserSettings(um); + } + + private setUserImage(image: string) { + this.store.setUserImage(image); + } + + /* + * Private hideGrowlProgress(key: number) { + * let progInter = this.progressInterval[key]; + * if (progInter) { + * this.logger.debug('Removing progressInterval {}', key)(); + * progInter.growl.hide(); + * if (progInter.interval) { + * clearInterval(progInter.interval); + * } + * delete this.progressInterval[key]; + * } + * } + */ + + + /* + * SendPreventDuplicates(data, skipGrowl) { + * this.messageId++; + * data.messageId = this.messageId; + * let jsonRequest = JSON.stringify(data); + * if (!this.duplicates[jsonRequest]) { + * this.duplicates[jsonRequest] = Date.now(); + * this.sendRawTextToServer(jsonRequest, skipGrowl, data); + * setTimeout(() => { + * delete this.duplicates[jsonRequest]; + * }, 5000); + * } else { + * this.logger.warn('blocked duplicate from sending: {}', jsonRequest)(); + * } + * } + */ + + + + + private sendToServer>(messageRequest: T, skipGrowl = false): boolean { + const isSent = this.wsMessageProcessor.sendToServer(messageRequest); + if (!isSent && !skipGrowl) { + this.logger.warn("Can't send message, because connection is lost :(")(); + } + return isSent; + } + + +} diff --git a/frontend/src/ts/message_handlers/WsHandler.ts b/frontend/src/ts/message_handlers/WsHandler.ts deleted file mode 100644 index 3da17c019..000000000 --- a/frontend/src/ts/message_handlers/WsHandler.ts +++ /dev/null @@ -1,770 +0,0 @@ -import { - CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT, - CONNECTION_RETRY_TIME, - FLAGS, - IS_DEBUG, - LOG_LEVEL_LS, -} from "@/ts/utils/consts"; -import type { - Logger, - LogLevel, -} from "lines-logger"; -import loggerFactory from "@/ts/instances/loggerFactory"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; -import type { - CurrentUserInfoWoImage, - CurrentUserSettingsModel, - Location, - MessageStatus, -} from "@/ts/types/model"; -import type { - MessageSupplier, - SessionHolder, -} from "@/ts/types/types"; -import { - convertLocation, - currentUserInfoDtoToModel, - userSettingsDtoToModel, -} from "@/ts/types/converters"; -import type { - GiphyDto, - RoomNoUsersDto, - UserProfileDto, - UserProfileDtoWoImage, - UserSettingsDto, -} from "@/ts/types/dto"; -import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import {WsMessageProcessor} from "@/ts/message_handlers/WsMessageProcessor"; -import type { - AddChannelMessage, - AddInviteMessage, - AddRoomMessage, - GetCountryCodeMessage, - MessagesResponseMessage, - PingMessage, - PongMessage, - PrintMessage, - SaveChannelSettingsMessage, - SetProfileImageMessage, - SetSettingsMessage, - SetUserProfileMessage, - SetWsIdMessage, - SyncHistoryResponseMessage, - UserProfileChangedMessage, - WebRtcSetConnectionIdMessage, -} from "@/ts/types/messages/wsInMessages"; -import type { - InternetAppearMessage, - LogoutMessage, - PubSetRooms, -} from "@/ts/types/messages/innerMessages"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - DefaultWsOutMessage, - SyncHistoryOutMessage, -} from "@/ts/types/messages/wsOutMessages"; -import type Subscription from "@/ts/classes/Subscription"; - -enum WsState { - NOT_INITED, TRIED_TO_CONNECT, CONNECTION_IS_LOST, CONNECTED, -} - -export default class WsHandler extends MessageHandler implements MessageSupplier { - protected readonly logger: Logger; - - protected readonly handlers: HandlerTypes = { - setSettings: > this.setSettings, - setUserProfile: > this.setUserProfile, - setProfileImage: > this.setProfileImage, - setWsId: > this.setWsId, - logout: > this.logout, - userProfileChanged: > this.userProfileChanged, - ping: > this.ping, - pong: > this.pong, - }; - - - /* - * How much current time is ahead of the server time - * if current time is in the past it will be negative - */ - private timeDiffWithServer: number = 0; - - private pingTimeoutFunction: number | null = null; - - private ws: WebSocket | null = null; - - private noServerPingTimeout: any; - - private readonly store: DefaultStore; - - private readonly sessionHolder: SessionHolder; - - private listenWsTimeout: number | null = null; - - private readonly API_URL: string; - - private readonly messageProc: WsMessageProcessor; - - private wsState: WsState = WsState.NOT_INITED; - - /* - * This.dom = { - * onlineStatus: $('onlineStatus'), - * onlineClass: 'online', - * offlineClass: OFFLINE_CLASS - * }; - * private progressInterval = {}; TODO this was commented along with usage, check if it breaks anything - */ - private wsConnectionId = ""; - - private readonly sub: Subscription; - - public constructor(API_URL: string, sessionHolder: SessionHolder, store: DefaultStore, sub: Subscription) { - super(); - this.sub = sub; - this.sub.subscribe("ws", this); - this.API_URL = API_URL; - this.messageProc = new WsMessageProcessor(this, store, "ws", sub); - this.logger = loggerFactory.getLoggerColor("ws", "#4c002b"); - this.sessionHolder = sessionHolder; - this.store = store; - } - - private get wsUrl() { - return `${this.API_URL}?id=${this.wsConnectionId}&sessionId=${this.sessionHolder.session}`; - } - - sendRawTextToServer(message: string): boolean { - if (this.isWsOpen()) { - this.ws!.send(message); - return true; - } - return false; - } - - public getWsConnectionId() { - return this.wsConnectionId; - } - - public async getCountryCode(): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "getCountryCode", - }); - } - - public async offerFile(roomId: number, browser: string, name: string, size: number, threadId: number | null): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "offerFile", - roomId, - threadId, - content: { - browser, - name, - size, - }, - }); - } - - public async offerCall(roomId: number, browser: string): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "offerCall", - roomId, - content: {browser}, - }); - } - - public acceptFile(connId: string, received: number) { - this.sendToServer({ - action: "acceptFile", - connId, - content: { - received, - }, - }); - } - - public replyFile(connId: string, browser: string) { - this.sendToServer({ - action: "replyFile", - connId, - content: {browser}, - }); - } - - public destroyFileConnection(connId: string, content: unknown) { - this.sendToServer({ - content, - action: "destroyFileConnection", - connId, - }); - } - - public destroyPeerFileConnection(connId: string, content: any, opponentWsId: string) { - this.sendToServer({ - content, - opponentWsId, - action: "destroyFileConnection", - connId, - }); - } - - public async search( - searchString: string, - roomId: number, - offset: number, - ): Promise { - return this.messageProc.sendToServerAndAwait({ - searchString, - roomId, - offset, - action: "searchMessages", - }); - } - - public sendEditMessage( - content: string | null, - id: number, - files: number[] | null, - tags: Record, - giphies: GiphyDto[], - ) { - const newVar = { - id, - action: "editMessage", - files, - tags, - giphies, - content, - }; - this.sendToServer(newVar, true); - } - - public async sendPrintMessage( - content: string, - roomId: number, - files: number[], - id: number, - timeDiff: number, - parentMessage: number | null, - tags: Record, - giphies: GiphyDto[], - ): Promise { - const newVar = { - files, - id, - timeDiff, - action: "printMessage", - content, - tags, - parentMessage, - roomId, - giphies, - }; - return this.messageProc.sendToServerAndAwait(newVar); - } - - public async saveSettings(content: UserSettingsDto): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "setSettings", - content, - }); - } - - public showIType(roomId: number): void { - this.sendToServer({ - roomId, - action: "showIType", - }); - } - - public async saveUser(content: UserProfileDtoWoImage): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "setUserProfile", - content, - }); - } - - public async sendAddRoom(name: string | null, p2p: boolean, volume: number, notifications: boolean, users: number[], channelId: number | null): Promise { - return this.messageProc.sendToServerAndAwait({ - users, - name, - p2p, - channelId, - action: "addRoom", - volume, - notifications, - }); - } - - public async syncHistory( - roomIds: number[], - messagesIds: number[], - receivedMessageIds: number[], - onServerMessageIds: number[], - lastSynced: number, - ): Promise { - const payload: SyncHistoryOutMessage = { - messagesIds, - receivedMessageIds, - onServerMessageIds, - roomIds, - lastSynced, - action: "syncHistory", - }; - return this.messageProc.sendToServerAndAwait(payload); - } - - public async sendAddChannel(channelName: string, users: number[]): Promise { - return this.messageProc.sendToServerAndAwait({ - channelName, - users, - action: "addChannel", - }); - } - - public async sendRoomSettings(message: RoomNoUsersDto): Promise { - return this.messageProc.sendToServerAndAwait({ - ...message, - action: "saveRoomSettings", - }); - } - - public async sendDeleteChannel(channelId: number): Promise { - return this.messageProc.sendToServerAndAwait({ - channelId, - action: "deleteChannel", - }); - } - - public async sendLeaveChannel(channelId: number): Promise { - return this.messageProc.sendToServerAndAwait({ - channelId, - action: "leaveChannel", - }); - } - - public async saveChannelSettings( - channelName: string, - channelId: number, - channelCreatorId: number, - volume: number, - notifications: boolean, - ): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "saveChannelSettings", - channelId, - channelCreatorId, - channelName, - volume, - notifications, - }); - } - - public async inviteUser(roomId: number, users: number[]): Promise { - return this.messageProc.sendToServerAndAwait({ - roomId, - users, - action: "inviteUser", - }); - } - - public async startListening(): Promise { - return new Promise((resolve, reject) => { - this.logger.log("Starting webSocket")(); - if (!this.listenWsTimeout && !this.ws) { - this.ws = new WebSocket(this.wsUrl); - this.ws.onmessage = this.onWsMessage.bind(this); - this.ws.onclose = (e) => { - setTimeout(() => { - reject(Error("Cannot connect to websocket")); - }); - this.onWsClose(e); - }; - this.ws.onopen = () => { - setTimeout(resolve); - this.onWsOpen(); - }; - } else { - resolve(); - } - }); - } - - public pingServer() { - this.sendToServer({action: "ping"}, true); - - /* - * TODO not used - * this.answerPong(); - * this.pingTimeoutFunction = setTimeout(() => { - * this.logger.error('Force closing socket coz pong time out')(); - * this.ws.close(1000, 'Ping timeout'); - * }, PING_CLOSE_JS_DELAY); - * - */ - } - - public logout(a: LogoutMessage) { - const info = []; - if (this.listenWsTimeout) { - this.listenWsTimeout = null; - info.push("purged timeout"); - } - if (this.ws) { - this.ws.onclose = null; - info.push("closed ws"); - this.ws.close(); - this.ws = null; - } - this.logger.debug("Finished ws: {}", info.join(", "))(); - } - - public async sendDeleteRoom(roomId: number) { - return this.messageProc.sendToServerAndAwait({ - roomId, - action: "deleteRoom", - }); - } - - public async sendLeaveRoom(roomId: number) { - return this.messageProc.sendToServerAndAwait({ - roomId, - action: "leaveUser", - }); - } - - public async setMessageStatus( - messagesIds: number[], - roomId: number, - status: MessageStatus, - ) { - return this.messageProc.sendToServerAndAwait({ - messagesIds, - action: "setMessageStatus", - status, - roomId, // This room event will be published to - }); - } - - public async sendLoadMessages( - roomId: number, - count: number, - threadId: number | null, - excludeIds: number[], - ): Promise { - return this.messageProc.sendToServerAndAwait({ - count, - excludeIds, - threadId, - action: "loadMessages", - roomId, - }); - } - - public async sendLoadMessagesByIds( - roomId: number, - messagesIds: number[], - ): Promise { - return this.messageProc.sendToServerAndAwait({ - messagesIds, - action: "loadMessagesByIds", - roomId, - }); - } - - public isWsOpen() { - return this.ws?.readyState === WebSocket.OPEN; - } - - public sendRtcData(content: RTCIceCandidate | RTCSessionDescriptionInit, connId: string, opponentWsId: string) { - this.sendToServer({ - content, - connId, - opponentWsId, - action: "sendRtcData", - }); - } - - public retry(connId: string, opponentWsId: string) { - this.sendToServer({ - action: "retryFile", - connId, - opponentWsId, - }); - } - - public replyCall(connId: string, browser: string) { - this.sendToServer({ - action: "replyCall", - connId, - content: { - browser, - }, - }); - } - - public destroyCallConnection(connId: string, content: "decline" | "hangup") { - this.sendToServer({ - content, - action: "destroyCallConnection", - connId, - }); - } - - public async offerMessageConnection(roomId: number): Promise { - return this.messageProc.sendToServerAndAwait({ - action: "offerMessage", - roomId, - }); - } - - public acceptCall(connId: string) { - this.sendToServer({ - action: "acceptCall", - connId, - }); - } - - public joinCall(connId: string) { - this.sendToServer({ - action: "joinCall", - connId, - }); - } - - public setSettings(m: SetSettingsMessage) { - const a: CurrentUserSettingsModel = userSettingsDtoToModel(m.content); - this.setUserSettings(a); - } - - public setUserProfile(m: SetUserProfileMessage) { - const a: CurrentUserInfoWoImage = currentUserInfoDtoToModel(m.content); - a.userId = this.store.userInfo!.userId; // This could came only when we logged in - this.store.setUserInfo(a); - } - - public setProfileImage(m: SetProfileImageMessage) { - this.setUserImage(m.content); - } - - public convertServerTimeToPC(serverTime: number) { - return serverTime + this.timeDiffWithServer; // ServerTime + (Date.now - serverTime) === Date.now - } - - public async setWsId(message: SetWsIdMessage) { - this.wsConnectionId = message.opponentWsId; - this.setUserInfo(message.userInfo); - this.setUserSettings(message.userSettings); - this.setUserImage(message.userInfo.userImage); - this.timeDiffWithServer = Date.now() - message.time; - const pubSetRooms: PubSetRooms = { - action: "init", - channels: message.channels, - handler: "room", - rooms: message.rooms, - online: message.online, - users: message.users, - }; - this.sub.notify(pubSetRooms); - const inetAppear: InternetAppearMessage = { - action: "internetAppear", - handler: "*", - }; - - this.sub.notify(inetAppear); - this.logger.debug("CONNECTION ID HAS BEEN SET TO {})", this.wsConnectionId)(); - if (FLAGS) { - const getCountryCodeMessage = await this.getCountryCode(); - const modelLocal: Record = {}; - Object.entries(getCountryCodeMessage.content).forEach(([k, v]) => { - modelLocal[k] = convertLocation(v); - }); - this.store.setCountryCode(modelLocal); - } - } - - public userProfileChanged(message: UserProfileChangedMessage) { - this.store.setUser({ - id: message.userId, - user: message.user, - image: message.userImage, - sex: message.sex, - }); - } - - public ping(message: PingMessage) { - this.startNoPingTimeout(); - this.sendToServer({ - action: "pong", - time: message.time, - }); - } - - public pong(message: PongMessage) { - // Private answerPong() { - if (this.pingTimeoutFunction) { - this.logger.debug("Clearing pingTimeoutFunction")(); - clearTimeout(this.pingTimeoutFunction); - this.pingTimeoutFunction = null; - } - // } - } - - notifyCallActive(param: {connectionId: string | null; opponentWsId: string; roomId: number}) { - this.sendToServer({ - action: "notifyCallActive", - connId: param.connectionId, - opponentWsId: param.opponentWsId, - roomId: param.roomId, - }); - } - - private setUserInfo(userInfo: UserProfileDto) { - const um: CurrentUserInfoWoImage = currentUserInfoDtoToModel(userInfo); - this.store.setUserInfo(um); - } - - private setUserSettings(userInfo: UserSettingsDto) { - const um: UserSettingsDto = userSettingsDtoToModel(userInfo); - const logLevel: LogLevel = userInfo.logs || (IS_DEBUG ? "trace" : "error"); - localStorage.setItem(LOG_LEVEL_LS, logLevel); - loggerFactory.setLogWarnings(logLevel); - this.store.setUserSettings(um); - } - - private setUserImage(image: string) { - this.store.setUserImage(image); - } - - /* - * Private hideGrowlProgress(key: number) { - * let progInter = this.progressInterval[key]; - * if (progInter) { - * this.logger.debug('Removing progressInterval {}', key)(); - * progInter.growl.hide(); - * if (progInter.interval) { - * clearInterval(progInter.interval); - * } - * delete this.progressInterval[key]; - * } - * } - */ - - - /* - * SendPreventDuplicates(data, skipGrowl) { - * this.messageId++; - * data.messageId = this.messageId; - * let jsonRequest = JSON.stringify(data); - * if (!this.duplicates[jsonRequest]) { - * this.duplicates[jsonRequest] = Date.now(); - * this.sendRawTextToServer(jsonRequest, skipGrowl, data); - * setTimeout(() => { - * delete this.duplicates[jsonRequest]; - * }, 5000); - * } else { - * this.logger.warn('blocked duplicate from sending: {}', jsonRequest)(); - * } - * } - */ - - private onWsOpen() { - this.setStatus(true); - this.startNoPingTimeout(); - this.wsState = WsState.CONNECTED; - this.logger.debug("Connection has been established")(); - } - - private onWsMessage(message: MessageEvent) { - const data = this.messageProc.parseMessage(message.data); - if (data) { - this.messageProc.handleMessage(data); - } - } - - private setStatus(isOnline: boolean) { - this.store.setIsOnline(isOnline); - this.logger.debug("Setting online to {}", isOnline)(); - } - - private onWsClose(e: CloseEvent) { - this.logger.log("Got onclose event")(); - this.ws = null; - this.setStatus(false); - // Tornado drops connection if exception occurs during processing an event we send from WsHandler - this.messageProc.onDropConnection(e.code === 1006 ? "Server error" : "Connection to server is lost"); - - /* - * For (let k in this.progressInterval) { - * this.hideGrowlProgress(k); - * } - */ - if (this.noServerPingTimeout) { - clearTimeout(this.noServerPingTimeout); - this.noServerPingTimeout = null; - } - const reason = e.reason || e; - if (e.code === 403) { - const message = `Server has forbidden request because '${reason}'. Logging out...`; - this.logger.error("onWsClose {}", message)(); - this.store.growlError(message); - const message1: LogoutMessage = { - action: "logout", - handler: "*", - }; - this.sub.notify(message1); - return; - } else if (this.wsState === WsState.NOT_INITED) { - // This.store.growlError( 'Can\'t establish connection with server'); - this.logger.warn("Chat server is down because {}", reason)(); - this.wsState = WsState.TRIED_TO_CONNECT; - } else if (this.wsState === WsState.CONNECTED) { - // This.store.growlError( `Connection to chat server has been lost, because ${reason}`); - this.logger.error( - "Connection to WebSocket has failed because \"{}\". Trying to reconnect every {}ms", - e.reason, - CONNECTION_RETRY_TIME, - )(); - } - if (this.wsState !== WsState.TRIED_TO_CONNECT) { - this.wsState = WsState.CONNECTION_IS_LOST; - } - // Try to reconnect in 10 seconds - this.listenWsTimeout = window.setTimeout(() => { - this.listenWS(); - }, CONNECTION_RETRY_TIME); - } - - private listenWS() { - this.ws = new WebSocket(this.wsUrl); - this.ws.onmessage = this.onWsMessage.bind(this); - this.ws.onclose = this.onWsClose.bind(this); - this.ws.onopen = this.onWsOpen.bind(this); - } - - private sendToServer>(messageRequest: T, skipGrowl = false): boolean { - const isSent = this.messageProc.sendToServer(messageRequest); - if (!isSent && !skipGrowl) { - this.logger.warn("Can't send message, because connection is lost :(")(); - } - return isSent; - } - - private startNoPingTimeout() { - if (this.noServerPingTimeout) { - clearTimeout(this.noServerPingTimeout); - this.logger.debug("Clearing noServerPingTimeout")(); - this.noServerPingTimeout = null; - } - this.noServerPingTimeout = setTimeout(() => { - if (this.ws) { - this.logger.error("Force closing socket coz server didn't ping us")(); - this.ws.close(1000, "Sever didn't ping us"); - } - }, CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT); - } -} diff --git a/frontend/src/ts/message_handlers/WsMessageHandler.ts b/frontend/src/ts/message_handlers/WsMessageHandler.ts index 1e14f7b9c..4b8732a1b 100644 --- a/frontend/src/ts/message_handlers/WsMessageHandler.ts +++ b/frontend/src/ts/message_handlers/WsMessageHandler.ts @@ -1,6 +1,18 @@ +import type {SaveFileRequest, SaveFileResponse} from "@common/http/file/save.file"; +import type {GiphyDto} from "@common/model/dto/giphy.dto"; +import type {MessageModelDto} from "@common/model/dto/message.model.dto"; +import {ImageType} from "@common/model/enum/image.type"; +import {MessageStatus} from "@common/model/enum/message.status"; +import type {MessagesResponseMessage} from "@common/model/ws.base"; +import type {DeleteMessageWsInMessage} from "@common/ws/message/ws-message/delete.message"; +import type {PrintMessageWsInMessage} from "@common/ws/message/ws-message/print.message"; +import type {SetMessageStatusWsInMessage} from "@common/ws/message/set.message.status"; +import type {SyncHistoryWsInMessage} from "@common/ws/message/sync.history"; +import type {HandlerName} from "@common/ws/common"; +import type {InternetAppearMessage} from "@/ts/types/messages/inner/internet.appear"; + import loggerFactory from "@/ts/instances/loggerFactory"; -import type Api from "@/ts/message_handlers/Api"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; +import type HttpApi from "@/ts/message_handlers/HttpApi"; import type { MessageSender, RemoveMessageProgress, @@ -8,71 +20,50 @@ import type { SetMessageProgress, SetMessageProgressError, SetUploadProgress, - UploadFile, } from "@/ts/types/types"; -import type { - FileModel, - MessageModel, - MessageStatus, - RoomModel, -} from "@/ts/types/model"; +import type {FileModel, MessageModel, RoomModel} from "@/ts/types/model"; +import {MessageStatusInner} from "@/ts/types/model"; import type {Logger} from "lines-logger"; -import type { - GiphyDto, - MessageModelDto, - SaveFileResponse, -} from "@/ts/types/dto"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; + +import type WsApi from "@/ts/message_handlers/WsApi"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type {InternetAppearMessage} from "@/ts/types/messages/innerMessages"; -import type { - HandlerName, - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - DeleteMessage, - EditMessage, - MessagesResponseMessage, - PrintMessage, - SetMessageStatusMessage, - SyncHistoryResponseMessage, -} from "@/ts/types/messages/wsInMessages"; + import {savedFiles} from "@/ts/utils/htmlApi"; import type {MessageHelper} from "@/ts/message_handlers/MessageHelper"; -import { - LAST_SYNCED, - MESSAGES_PER_SEARCH, -} from "@/ts/utils/consts"; +import {LAST_SYNCED, MESSAGES_PER_SEARCH} from "@/ts/utils/consts"; import {convertMessageModelDtoToModel} from "@/ts/types/converters"; -import { - checkIfIdIsMissing, - getMissingIds, -} from "@/ts/utils/pureFunctions"; +import {checkIfIdIsMissing, getMissingIds} from "@/ts/utils/pureFunctions"; import type Subscription from "@/ts/classes/Subscription"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {DeleteMessageWsInBody} from "@common/ws/message/ws-message/delete.message"; +import {SetMessageStatusWsInBody} from "@common/ws/message/set.message.status"; + -export default class WsMessageHandler extends MessageHandler implements MessageSender { +export default class WsMessageHandler implements MessageSender { protected readonly logger: Logger; protected readonly handlers: HandlerTypes = { - deleteMessage: > this.deleteMessage, editMessage: > this.editMessage, printMessage: > this.printMessage, internetAppear: > this.internetAppear, - setMessageStatus: > this.setMessageStatus, }; + public async internetAppear(m: InternetAppearMessage) { + this.syncMessages(); // If message was edited, or changed by server + this.syncHistory(); // If some messages were sent during offline + } + /* * MessageRetrier uses MessageModel.id as unique identifier, do NOT use it with any types but * Send message, delete message, edit message, as it will have the sameId which could erase some callback */ private readonly store: DefaultStore; - private readonly api: Api; + private readonly api: HttpApi; - private readonly ws: WsHandler; + private readonly ws: WsApi; private syncMessageLock: boolean = false; @@ -80,12 +71,11 @@ export default class WsMessageHandler extends MessageHandler implements MessageS public constructor( store: DefaultStore, - api: Api, - ws: WsHandler, + api: HttpApi, + ws: WsApi, messageHelper: MessageHelper, sub: Subscription, ) { - super(); this.store = store; this.api = api; sub.subscribe("ws-message", this); @@ -98,7 +88,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS await this.ws.setMessageStatus( messagesIds, roomId, - "read", + MessageStatus.READ, ); } @@ -114,7 +104,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS if (room.p2p) { continue; } - const messages = Object.values(room.messages).filter((m) => m.status === "sending"); + const messages = Object.values(room.messages).filter((m) => m.status === MessageStatusInner.SENDING); // Sync messages w/o parent thread first messages.sort((a, b) => { if (a.parentMessage && !b.parentMessage) { @@ -201,20 +191,20 @@ export default class WsMessageHandler extends MessageHandler implements MessageS const fileIds: number[] = this.getFileIdsFromMessage(storeMessage); const giphies: GiphyDto[] = this.getGiphiesFromMessage(storeMessage); if (storeMessage.id < 0 && storeMessage.content) { - const a: PrintMessage = await this.ws.sendPrintMessage( - storeMessage.content, + const a: PrintMessageWsInMessage = await this.ws.sendPrintMessage({ + content: storeMessage.content, roomId, - fileIds, - storeMessage.id, - Date.now() - storeMessage.time, - storeMessage.parentMessage, - {...storeMessage.tags}, + files: fileIds, + id: storeMessage.id, + timeDiff: Date.now() - storeMessage.time, + parentMessage: storeMessage.parentMessage, + tags: {...storeMessage.tags}, giphies, - ); + }); const rmMes: RoomMessageIds = { messageId: storeMessage.id, roomId: storeMessage.roomId, - newMessageId: a.id, + newMessageId: a.message.id, }; this.store.deleteMessage(rmMes); } else if (storeMessage.id > 0) { @@ -227,10 +217,9 @@ export default class WsMessageHandler extends MessageHandler implements MessageS public async uploadFiles( messageId: number, roomId: number, - files: UploadFile[], - ): Promise { - let size: number = 0; - files.forEach((f) => size += f.file.size); + files: SaveFileRequest[], + ): Promise { + const size = files.reduce((summ, f) => summ + f.file.size, 0); const sup: SetUploadProgress = { upload: { total: size, @@ -240,37 +229,39 @@ export default class WsMessageHandler extends MessageHandler implements MessageS roomId, }; this.store.setUploadProgress(sup); + const responses: SaveFileResponse[] = []; try { - const res: SaveFileResponse = await this.api.uploadFiles( - files, - (evt) => { - if (evt.lengthComputable) { + let total = 0; + for (let i = 0; i < files.length; i++) { + const res: SaveFileResponse = await this.api.fileApi.uploadFile( + files[i], + (uploaded) => { const payload: SetMessageProgress = { messageId, roomId, - uploaded: evt.loaded, + uploaded: total + uploaded, }; this.store.setMessageProgress(payload); - } - }, - (xhr) => { - this.store.setUploadXHR({ - xhr, - messageId, - roomId, - }); - }, - ); + }, + (abortFunction) => { + this.store.setUploadXHR({ + abortFunction, + messageId, + roomId, + }); + }, + ); + total += files[i].file.size; + responses.push(res); + } + const newVar: RemoveMessageProgress = { messageId, roomId, }; this.store.removeMessageProgress(newVar); - if (!res || Object.keys(res).length === 0) { - throw Error("Missing files uploads"); - } - return res; + return responses; } catch (error: any) { const newVar: SetMessageProgressError = { messageId, @@ -304,7 +295,8 @@ export default class WsMessageHandler extends MessageHandler implements MessageS }); } - public deleteMessage(inMessage: DeleteMessage) { + @Subscribe() + public deleteMessage(inMessage: DeleteMessageWsInBody) { let message: MessageModel = this.store.roomsDict[inMessage.roomId].messages[inMessage.id]; if (!message) { this.logger.warn("Unable to find message {} to delete it", inMessage)(); @@ -323,7 +315,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS symbol: message.symbol || null, threadMessagesCount: message.threadMessagesCount, isHighlighted: false, - status: message.status === "sending" ? "on_server" : message.status, + status: message.status === MessageStatusInner.SENDING ? MessageStatus.ON_SERVER : message.status, edited: inMessage.edited, roomId: message.roomId, userId: message.userId, @@ -349,7 +341,8 @@ export default class WsMessageHandler extends MessageHandler implements MessageS } } - public async printMessage(inMessage: PrintMessage) { + public async printMessage(request: PrintMessageWsInMessage) { + const inMessage: MessageModelDto = request.message; const message: MessageModel = convertMessageModelDtoToModel(inMessage, null, (time) => this.ws.convertServerTimeToPC(time)); this.messageHelper.processUnknownP2pMessage(message); if (inMessage.userId !== this.store.myId) { @@ -357,7 +350,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS this.ws.setMessageStatus( [inMessage.id], inMessage.roomId, - isRead ? "read" : "received", + isRead ? MessageStatus.READ : MessageStatus.RECEIVED, ); } if (checkIfIdIsMissing(message, this.store)) { @@ -365,12 +358,8 @@ export default class WsMessageHandler extends MessageHandler implements MessageS } } - public async internetAppear(m: InternetAppearMessage) { - this.syncMessages(); // If message was edited, or changed by server - this.syncHistory(); // If some messages were sent during offline - } - - public async setMessageStatus(m: SetMessageStatusMessage) { + @Subscribe() + public async setMessageStatus(m: SetMessageStatusWsInBody) { this.store.setMessagesStatus({ roomId: m.roomId, status: m.status, @@ -382,7 +371,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS if (!storeMessage.files) { return []; } - return Object.entries(storeMessage.files).filter(([k, v]) => v.type === "g" && !v.serverId). + return Object.entries(storeMessage.files).filter(([k, v]) => v.type === ImageType.GIPHY && !v.serverId). map(([k, v]) => ({ url: v.url!, symbol: k, @@ -397,7 +386,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS if (fileValues.find((f) => !f.fileId && f.sending)) { throw Error("New files were added during upload"); // TODO } - fileValues.filter((fv) => fv.type !== "g").forEach((fv) => { + fileValues.filter((fv) => fv.type !== ImageType.GIPHY).forEach((fv) => { files.push(fv.fileId!); if (fv.previewFileId) { files.push(fv.previewFileId); @@ -409,27 +398,36 @@ export default class WsMessageHandler extends MessageHandler implements MessageS private async uploadFilesForMessages(storeMessage: MessageModel) { if (storeMessage.files) { - const uploadFiles: UploadFile[] = []; + const uploadFiles: SaveFileRequest[] = []; Object.keys(storeMessage.files).filter((k) => !storeMessage.files![k].fileId && storeMessage.files![k].sending). - forEach((k) => { - const file: FileModel = storeMessage.files![k]; - uploadFiles.push({ - file: savedFiles[file.url!], // TODO why null? - key: file.type + k, - }); + forEach((key) => { + const file: FileModel = storeMessage.files![key]; + const items: SaveFileRequest = { + file: savedFiles[file.url!], + symbol: key, + name: savedFiles[file.url!].name!, + type: file.type, + }; + uploadFiles.push(items); if (file.preview) { - uploadFiles.push({ - file: savedFiles[file.preview], - key: `p${k}`, - }); + const previewFile = savedFiles[file.preview]; + const payload: SaveFileRequest = { + file: previewFile, + type: ImageType.PREVIEW, + symbol: key, + }; + if (previewFile.name) { + payload.name = previewFile.name; + } + uploadFiles.push(payload); } }); if (uploadFiles.length > 0) { - const fileIds = await this.uploadFiles(storeMessage.id, storeMessage.roomId, uploadFiles); + const files: SaveFileResponse[] = await this.uploadFiles(storeMessage.id, storeMessage.roomId, uploadFiles); this.store.setMessageFileIds({ roomId: storeMessage.roomId, messageId: storeMessage.id, - fileIds, + files, }); } } @@ -467,8 +465,8 @@ export default class WsMessageHandler extends MessageHandler implements MessageS let roomMessage = Object.values(r.messages).filter((m) => m.id > 0); messagesIds.push(...roomMessage.map((m) => m.id)); roomMessage = roomMessage.filter((u) => u.userId === this.store.myId); - receivedMessageIds.push(...roomMessage.filter((m) => m.status === "received").map((m) => m.id)); - onServerMessageIds.push(...roomMessage.filter((m) => m.status === "on_server").map((m) => m.id)); + receivedMessageIds.push(...roomMessage.filter((m) => m.status === MessageStatus.RECEIVED).map((m) => m.id)); + onServerMessageIds.push(...roomMessage.filter((m) => m.status === MessageStatus.ON_SERVER).map((m) => m.id)); return r.id; }); @@ -480,7 +478,7 @@ export default class WsMessageHandler extends MessageHandler implements MessageS } joined = parseInt(joined); - const result: SyncHistoryResponseMessage = await this.ws.syncHistory( + const result: SyncHistoryWsInMessage = await this.ws.syncHistory( roomIds, messagesIds, receivedMessageIds, @@ -489,10 +487,10 @@ export default class WsMessageHandler extends MessageHandler implements MessageS ); // Updating information if message that I sent were received/read - this.setAllMessagesStatus(result.readMessageIds, "read"); - this.setAllMessagesStatus(result.receivedMessageIds, "received"); + this.setAllMessagesStatus(result.readMessageIds, MessageStatus.READ); + this.setAllMessagesStatus(result.receivedMessageIds, MessageStatus.RECEIVED); - const messagesByStatus = this.groupMessagesIdsByStatus(result.content, () => true); + const messagesByStatus = this.groupMessagesIdsByStatus(result.messages, () => true); Object.entries(messagesByStatus).forEach(([k, messagesInGroup]) => { const roomId = parseInt(k); this.addMessages(roomId, messagesInGroup, true); @@ -529,11 +527,11 @@ export default class WsMessageHandler extends MessageHandler implements MessageS * If we received a message from the server from our second device * We still don't need to mark those messages as received */ - filter((m) => m.userId !== this.store.myId && (m.status === "received" || m.status === "on_server")).map((m) => m.id); - messageStatus = "read"; + filter((m) => m.userId !== this.store.myId && (m.status === MessageStatus.RECEIVED || m.status === MessageStatus.ON_SERVER)).map((m) => m.id); + messageStatus = MessageStatus.READ; } else { - ids = inMessages.filter((m) => m.status === "on_server").map((m) => m.id); - messageStatus = "received"; + ids = inMessages.filter((m) => m.status === MessageStatus.ON_SERVER).map((m) => m.id); + messageStatus = MessageStatus.RECEIVED; } if (ids.length > 0) { this.ws.setMessageStatus( diff --git a/frontend/src/ts/message_handlers/WsMessageProcessor.ts b/frontend/src/ts/message_handlers/WsMessageProcessor.ts index e1614240e..8aa700b3a 100644 --- a/frontend/src/ts/message_handlers/WsMessageProcessor.ts +++ b/frontend/src/ts/message_handlers/WsMessageProcessor.ts @@ -1,46 +1,208 @@ +import type {GrowlWsInMessage} from "@common/ws/message/growl.message"; +import type {ResponseWsInMessage} from "@common/ws/common"; import AbstractMessageProcessor from "@/ts/message_handlers/AbstractMessageProcessor"; -import type { - DefaultWsInMessage, - GrowlMessage, -} from "@/ts/types/messages/wsInMessages"; -import type {HandlerName} from "@/ts/types/messages/baseMessagesInterfaces"; import type Subscription from "@/ts/classes/Subscription"; -import type {MessageSupplier} from "@/ts/types/types"; +import { + CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT, + CONNECTION_RETRY_TIME, +} from "@/ts/utils/consts"; +import {Subscribe} from "@/ts/utils/pubsub"; +import type {LogoutMessage} from "@/ts/types/messages/inner/logout"; +import {WS_SESSION_EXPIRED_CODE} from "@common/consts"; +import {WsState} from "@/ts/types/model"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; +import {SessionHolder} from "@/ts/types/types"; + export class WsMessageProcessor extends AbstractMessageProcessor { - private readonly sub: Subscription; + private wsState: WsState = WsState.NOT_INITED; + + private listenWsTimeout: number | null = null; + + private ws: WebSocket | null = null; + + private noServerPingTimeout: number | null = null; + + private readonly wsConnectionId = ""; + + public constructor( + private readonly apiUrl: string, + private readonly sub: Subscription, + private readonly store: DefaultStore, + private readonly sessionHolder: SessionHolder, + ) { + super("ws"); + } + + private onWsClose(e: CloseEvent) { + this.logger.log("Got onclose event")(); + this.ws = null; + this.setStatus(false); + // Tornado drops connection if exception occurs during processing an event we send from WsApi + this.onDropConnection(e.code === 1006 ? "Server error" : "Connection to server is lost"); + + /* + * For (let k in this.progressInterval) { + * this.hideGrowlProgress(k); + * } + */ + if (this.noServerPingTimeout) { + clearTimeout(this.noServerPingTimeout); + this.noServerPingTimeout = null; + } + const reason = e.reason || e; + if (e.code === WS_SESSION_EXPIRED_CODE) { + const message = `Server has forbidden request because '${reason}'. Logging out...`; + this.logger.error("onWsClose {}", message)(); + this.store.growlError(message); + const message1: LogoutMessage = { + action: "logout", + handler: "*", + data: null, + }; + this.sub.notify(message1); + return; + } else if (this.wsState === WsState.NOT_INITED) { + // This.store.growlError( 'Can\'t establish connection with server'); + this.logger.warn("Chat server is down because {}", reason)(); + this.wsState = WsState.TRIED_TO_CONNECT; + } else if (this.wsState === WsState.CONNECTED) { + // This.store.growlError( `Connection to chat server has been lost, because ${reason}`); + this.logger.error( + "Connection to WebSocket has failed because \"{}\". Trying to reconnect every {}ms", + e.reason, + CONNECTION_RETRY_TIME, + )(); + } + if (this.wsState !== WsState.TRIED_TO_CONNECT) { + this.wsState = WsState.CONNECTION_IS_LOST; + } + // Try to reconnect in 10 seconds + this.listenWsTimeout = window.setTimeout(() => { + this.listenWS(); + }, CONNECTION_RETRY_TIME); + } + + private setStatus(isOnline: boolean) { + this.store.setIsOnline(isOnline); + this.logger.debug("Setting online to {}", isOnline)(); + } + + private onWsOpen() { + this.setStatus(true); + this.startNoPingTimeout(); + this.wsState = WsState.CONNECTED; + this.logger.debug("Connection has been established")(); + } + + private listenWS() { + this.ws = new WebSocket(this.wsUrl); + this.ws.onmessage = this.onWsMessage.bind(this); + this.ws.onclose = this.onWsClose.bind(this); + this.ws.onopen = this.onWsOpen.bind(this); + } + + private onWsMessage(message: MessageEvent) { + const data = this.parseMessage(message.data); + if (data) { + this.handleMessage(data); + } + } - public constructor(target: MessageSupplier, store: DefaultStore, label: string, sub: Subscription) { - super(target, store, label); - this.sub = sub; + @Subscribe() + public logout() { + const info = []; + if (this.listenWsTimeout) { + this.listenWsTimeout = null; + info.push("purged timeout"); + } + if (this.ws) { + this.ws.onclose = null; + info.push("closed ws"); + this.ws.close(); + this.ws = null; + } + this.logger.debug("Finished ws: {}", info.join(", "))(); } - public handleMessage(data: DefaultWsInMessage) { - if (data.handler !== "void" && data.action !== "growlError") { - this.sub.notify(data); + private startNoPingTimeout() { + if (this.noServerPingTimeout) { + clearTimeout(this.noServerPingTimeout); + this.logger.debug("Clearing noServerPingTimeout")(); + this.noServerPingTimeout = null; } - if (data.cbId && this.callBacks[data.cbId] && (!data.cbBySender || data.cbBySender === this.target.getWsConnectionId())) { + this.noServerPingTimeout = setTimeout(() => { + if (this.ws) { + this.logger.error("Force closing socket coz server didn't ping us")(); + this.ws.close(1000, "Sever didn't ping us"); + } + }, CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT) as unknown as number; + } + + sendRawTextToServer(message: string): boolean { + if (this.isWsOpen()) { + this.ws!.send(message); + return true; + } + return false; + } + + public isWsOpen() { + return this.ws?.readyState === WebSocket.OPEN; + } + + public handleMessage(data: unknown) { + const responseWsInMessage: ResponseWsInMessage = data as ResponseWsInMessage; + const growlErrorMessage: GrowlWsInMessage = data as GrowlWsInMessage; + if (responseWsInMessage.cbId && + this.callBacks[responseWsInMessage.cbId] && + (!responseWsInMessage.cbBySender || responseWsInMessage.cbBySender === this.wsConnectionId) + ) { this.logger.debug("resolving cb")(); - if (data.action === "growlError") { - this.callBacks[data.cbId].reject(Error((data as unknown as GrowlMessage).content)); + if (growlErrorMessage.action === "growlError") { + this.callBacks[growlErrorMessage.cbId].reject(Error(growlErrorMessage.data.error)); } else { - this.callBacks[data.cbId].resolve(data); + this.callBacks[responseWsInMessage.cbId].resolve(responseWsInMessage.data); } - delete this.callBacks[data.cbId]; - } else if (data.action === "growlError") { - // GrowlError is used only in case above, so this is just a fallback that will never happen - this.store.growlError((data as unknown as GrowlMessage).content); + delete this.callBacks[responseWsInMessage.cbId]; + } else { + this.sub.notify(responseWsInMessage); } } - public parseMessage(jsonData: string): DefaultWsInMessage | null { - const data: DefaultWsInMessage | null = super.parseMessage(jsonData); - if (data?.handler !== "void" && (!data?.handler || !data.action)) { - this.logger.error("Invalid message structure")(); + private get wsUrl() { + return `${this.apiUrl}?id=${this.wsConnectionId}&sessionId=${this.sessionHolder.session}`; + } - return null; - } - return data; + setWsConnectionId(id: string) { + return this.wsConnectionId; + } + + public getWsConnectionId() { + return this.wsConnectionId; + } + + + public async startListening(): Promise { + return new Promise((resolve, reject) => { + this.logger.log("Starting webSocket")(); + if (!this.listenWsTimeout && !this.ws) { + this.ws = new WebSocket(this.wsUrl); + this.ws.onmessage = this.onWsMessage.bind(this); + this.ws.onclose = (e) => { + setTimeout(() => { + reject(Error("Cannot connect to websocket")); + }); + this.onWsClose(e); + }; + this.ws.onopen = () => { + setTimeout(resolve); + this.onWsOpen(); + }; + } else { + resolve(); + } + }); } + } diff --git a/frontend/src/ts/message_handlers/http_modules/AuthApi.ts b/frontend/src/ts/message_handlers/http_modules/AuthApi.ts new file mode 100644 index 000000000..82cbdb51c --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/AuthApi.ts @@ -0,0 +1,68 @@ +import type {FaceBookSignInRequest, FacebookSignInResponse} from "@common/http/auth/facebook.sign.in"; +import type {GoogleSignInRequest, GoogleSignInResponse} from "@common/http/auth/google.sign.in"; +import type {SignInRequest} from "@common/http/auth/sign.in"; +import type {SignUpRequest, SignUpResponse} from "@common/http/auth/sign.up"; +import type {ValidateUserRequest, ValidateUserResponse} from "@common/http/auth/validate.user"; +import type {ValidateEmailResponse, ValidateUserEmailRequest} from "@common/http/auth/validte.email"; +import type {SessionResponse} from "@common/model/http.base"; + + +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; +import type Fetch from "@/ts/classes/Fetch"; + + +export default class AuthApi { + protected readonly logger: Logger; + + private readonly fetch: Fetch; + + public constructor(fetch: Fetch) { + this.logger = loggerFactory.getLogger("api"); + this.fetch = fetch; + } + + public async signIn(body: SignInRequest): Promise { + return this.fetch.doPost({ + url: "/sign-in", + params: {...body}, + }); + } + + public async signUp(data: SignUpRequest): Promise { + return this.fetch.doPost({ + url: "/sign-up", + params: data as any, + }); + } + + public async validateUsername(params: ValidateUserRequest, onSetAbortFunction: (c: () => void) => void): Promise { + return this.fetch.doPost({ + url: "/validate-user", + params, + onSetAbortFunction, + }); + } + + public async googleAuth(params: GoogleSignInRequest): Promise { + return this.fetch.doPost({ + url: "/google-sign-in", + params, + }); + } + + public async facebookAuth(params: FaceBookSignInRequest): Promise { + return this.fetch.doPost({ + url: "/facebook-sign-in", + params, + }); + } + + public async validateEmail(params: ValidateUserEmailRequest, onSetAbortFunction: (c: () => void) => void): Promise { + return this.fetch.doPost({ + url: "/validate-email", + params, + onSetAbortFunction, + }); + } +} diff --git a/frontend/src/ts/message_handlers/http_modules/FileApi.ts b/frontend/src/ts/message_handlers/http_modules/FileApi.ts new file mode 100644 index 000000000..a8df88231 --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/FileApi.ts @@ -0,0 +1,27 @@ +import type {SaveFileRequest, SaveFileResponse} from "@common/http/file/save.file"; + + +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; +import type Fetch from "@/ts/classes/Fetch"; + + +export default class FileApi { + protected readonly logger: Logger; + + private readonly fetch: Fetch; + + public constructor(fetch: Fetch) { + this.logger = loggerFactory.getLogger("api"); + this.fetch = fetch; + } + + public async uploadFile(data: SaveFileRequest, onProgress: (i: number) => void, onSetAbortFunction: (e: () => void) => void): Promise { + return this.fetch.upload({ + url: "/upload-file", + data, + onSetAbortFunction, + onProgress, + }); + } +} diff --git a/frontend/src/ts/message_handlers/http_modules/GiphyApi.ts b/frontend/src/ts/message_handlers/http_modules/GiphyApi.ts new file mode 100644 index 000000000..6cb52bc48 --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/GiphyApi.ts @@ -0,0 +1,47 @@ +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; +import type {MultiResponse} from "giphy-api"; +import type Fetch from "@/ts/classes/Fetch"; + +export default class GiphyApi { + protected readonly logger: Logger; + + private readonly fetch: Fetch; + + private readonly apiKey: string; + + public constructor(fetch: Fetch, apiKey: string) { + this.fetch = fetch; + this.apiKey = apiKey; + this.logger = loggerFactory.getLogger("api"); + } + + public async searchGiphys( + text: string, + offset: number, + limit: number, + onSetAbortFunction: (c: () => void) => void, + ): Promise { + let response!: MultiResponse; + if ((/^\s*$/).exec(text)) { + // https://developers.giphy.com/docs/api/endpoint#trending + response = await this.fetch.doGet( + `/trending?api_key=${this.apiKey}&limit=${limit}&offset=${offset}`, + onSetAbortFunction, + ); + } else { + // https://developers.giphy.com/docs/api/endpoint#search + response = await this.fetch.doGet( + `/search?api_key=${this.apiKey}&limit=12&q=${encodeURIComponent(text)}&offset=${offset}`, + onSetAbortFunction, + ); + } + + if (response?.meta?.msg === "OK") { + return response; + } + throw Error(`Invalid giphy response ${response?.meta?.msg}`); + } +} + + diff --git a/frontend/src/ts/message_handlers/http_modules/JsApi.ts b/frontend/src/ts/message_handlers/http_modules/JsApi.ts new file mode 100644 index 000000000..3d9c8f330 --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/JsApi.ts @@ -0,0 +1,37 @@ +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; + +export default class JsApi { + protected readonly logger: Logger; + + public constructor() { + this.logger = loggerFactory.getLogger("api"); + } + + public async loadGoogle(): Promise { + await this.loadJs("https://apis.google.com/js/platform.js"); + } + + public async loadFacebook(): Promise { + await this.loadJs("https://connect.facebook.net/en_US/sdk.js"); + } + + public async loadRecaptcha(callbackId: string): Promise { + await this.loadJs(`https://www.google.com/recaptcha/api.js?render=explicit&onload=${callbackId}`); + } + + public async loadJs(fullFileUrlWithProtocol: string): Promise { + return new Promise((resolve, reject) => { + this.logger.log("GET out {}", fullFileUrlWithProtocol)(); + const fileRef = document.createElement("script"); + fileRef.setAttribute("type", "text/javascript"); + fileRef.setAttribute("src", fullFileUrlWithProtocol); + + document.getElementsByTagName("head")[0].appendChild(fileRef); + fileRef.onload = resolve; + fileRef.onerror = reject; + }); + } +} + + diff --git a/frontend/src/ts/message_handlers/http_modules/RestApi.ts b/frontend/src/ts/message_handlers/http_modules/RestApi.ts new file mode 100644 index 000000000..d775d504a --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/RestApi.ts @@ -0,0 +1,87 @@ +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; +import type Fetch from "@/ts/classes/Fetch"; +import type {OauthStatus, ViewUserProfileDto} from "@/ts/types/dto"; + +export default class RestApi { + protected readonly logger: Logger; + + private readonly fetch: Fetch; + + public constructor(fetch: Fetch) { + this.logger = loggerFactory.getLogger("api"); + this.fetch = fetch; + } + + public async getOauthStatus(): Promise { + return this.fetch.doGet("/oauth_status"); + } + + public async setGoogleOauth(token: string): Promise { + return this.fetch.doPost({ + url: "/set_google_oauth", + params: {token}, + }); + } + + public async setFacebookOauth(token: string): Promise { + return this.fetch.doPost({ + url: "/set_facebook_oauth", + params: {token}, + }); + } + + public async registerFCM(registration_id: string, agent: string, is_mobile: boolean): Promise { + await this.fetch.doPost({ + url: "/register_fcb", + params: { + registration_id, + agent, + is_mobile, + }, + }); + } + + + public async uploadProfileImage(file: Blob): Promise { + await this.fetch.upload({ + url: "/upload_profile_image", + data: {file}, + }); + } + + public async showProfile(id: number): Promise { + return this.fetch.doGet(`/profile?id=${id}`); + } + + public async changeEmail(token: string): Promise { + return this.fetch.doGet(`/change_email?token=${token}`); + } + + public async changeEmailLogin(email: string, password: string): Promise { + return this.fetch.doPost({ + url: "/change_email_login", + params: { + email, + password, + }, + }); + } + + public async changePassword(old_password: string, password: string): Promise { + return this.fetch.doPost({ + url: "/change_password", + params: { + old_password, + password, + }, + }); + } + + public async logout(registration_id: string | null = null): Promise { + await this.fetch.doPost({ + url: "/logout", + params: {registration_id}, + }); + } +} diff --git a/frontend/src/ts/message_handlers/http_modules/VerifyApi.ts b/frontend/src/ts/message_handlers/http_modules/VerifyApi.ts new file mode 100644 index 000000000..004c3f010 --- /dev/null +++ b/frontend/src/ts/message_handlers/http_modules/VerifyApi.ts @@ -0,0 +1,50 @@ +import type {AcceptTokenRequest, AcceptTokenResponse} from "@common/http/verify/accept.token"; +import type {ConfirmEmailRequest, ConfirmEmailResponse} from "@common/http/verify/confirm.email"; +import type {SendRestorePasswordRequest, SendRestorePasswordResponse} from "@common/http/verify/send.restore.password"; +import type {VerifyTokenRequest, VerifyTokenResponse} from "@common/http/verify/verify.token"; + + +import loggerFactory from "@/ts/instances/loggerFactory"; +import type {Logger} from "lines-logger"; +import type Fetch from "@/ts/classes/Fetch"; + + +export default class VerifyApi { + protected readonly logger: Logger; + + private readonly fetch: Fetch; + + public constructor(fetch: Fetch) { + this.logger = loggerFactory.getLogger("api"); + this.fetch = fetch; + } + + public async verifyToken(params: VerifyTokenRequest): Promise { + return this.fetch.doPost({ + url: "/verify-token", + params, + }); + } + + public async confirmEmail(params: ConfirmEmailRequest): Promise { + return this.fetch.doPost({ + url: "/confirm-email", + params, + }); + } + + + public async acceptToken(params: AcceptTokenRequest): Promise { + return this.fetch.doPost({ + url: "/accept-token", + params, + }); + } + + public async sendRestorePassword(params: SendRestorePasswordRequest): Promise { + return this.fetch.doPost({ + url: "/send-restore-password", + params, + }); + } +} diff --git a/frontend/src/ts/types/converters.ts b/frontend/src/ts/types/converters.ts index 23517d64e..499891318 100644 --- a/frontend/src/ts/types/converters.ts +++ b/frontend/src/ts/types/converters.ts @@ -1,3 +1,13 @@ +import type {ChannelDto} from "@common/model/dto/channel.dto"; +import type {FileModelDto} from "@common/model/dto/file.model.dto"; +import type {LocationDto} from "@common/model/dto/location.dto"; +import type {MessageModelDto} from "@common/model/dto/message.model.dto"; +import type {RoomDto, RoomNoUsersDto} from "@common/model/dto/room.dto"; +import type {UserDto} from "@common/model/dto/user.dto"; +import type {UserProfileDtoWoImage} from "@common/model/dto/user.profile.dto"; +import type {UserSettingsDto} from "@common/model/dto/user.settings.dto"; +import type {Gender} from "@common/model/enum/gender"; +import {MessageStatus} from "@common/model/enum/message.status"; import type { ChannelModel, CurrentUserInfoModel, @@ -6,30 +16,15 @@ import type { FileModel, Location, MessageModel, - MessageStatus, RoomModel, RoomSettingsModel, - SexModelString, UserModel, } from "@/ts/types/model"; -import type { - ChannelDto, - FileModelDto, - LocationDto, - MessageModelDto, - RoomDto, - RoomNoUsersDto, - SexModelDto, - UserDto, - UserProfileDtoWoImage, - UserSettingsDto, -} from "@/ts/types/dto"; -import type { - BooleanDB, - SexDB, -} from "@/ts/types/db"; + +import type {BooleanDB} from "@/ts/types/db"; import type {MessageP2pDto} from "@/ts/types/messages/p2pDto"; + export function currentUserInfoDtoToModel(userInfo: UserProfileDtoWoImage): CurrentUserInfoWoImage { return {...userInfo}; } @@ -42,7 +37,7 @@ export function currentUserInfoModelToDto(userInfo: CurrentUserInfoModel): UserP return {...userInfo}; } -export function convertSex(dto: SexModelDto): SexModelString { +export function convertSex(dto: Gender): Gender { return dto; } @@ -58,29 +53,18 @@ export function convertLocation(dto: LocationDto | null): Location { return {...dto}; } -export function convertSexToNumber(m: SexModelString): number { - if (m === "Secret") { - return 0; - } else if (m === "Male") { - return 1; - } else if (m === "Female") { - return 2; - } - throw Error(`Unknown gender ${m}`); -} - export function getChannelDict( { - channelName, - channelCreatorId, - channelId, + name, + creatorId, + id, }: ChannelDto, oldChannel: ChannelModel | null = null, ): ChannelModel { return { - name: channelName, - id: channelId, - creator: channelCreatorId, + name, + id, + creatorId, expanded: oldChannel?.expanded ?? false, }; } @@ -89,23 +73,23 @@ export function getRoom(r: RoomNoUsersDto): RoomSettingsModel { return { channelId: r.channelId, p2p: r.p2p, - id: r.roomId, + id: r.id, name: r.name, isMainInChannel: r.isMainInChannel, notifications: r.notifications, volume: r.volume, - creator: r.roomCreatorId, + creatorId: r.creatorId, }; } export function getRoomsBaseDict( { - roomId, + id, volume, channelId, isMainInChannel, notifications, - roomCreatorId, + creatorId, p2p, name, users, @@ -113,7 +97,7 @@ export function getRoomsBaseDict( databaseRestoredRoom: RoomModel | null = null, ): RoomModel { return { - id: roomId, + id, receivingFiles: databaseRestoredRoom ? databaseRestoredRoom.receivingFiles : {}, sendingFiles: databaseRestoredRoom ? databaseRestoredRoom.sendingFiles : {}, channelId, @@ -140,7 +124,7 @@ export function getRoomsBaseDict( }, notifications, name, - creator: roomCreatorId, + creatorId, messages: databaseRestoredRoom ? databaseRestoredRoom.messages : {}, roomLog: databaseRestoredRoom ? databaseRestoredRoom.roomLog : [], changeName: databaseRestoredRoom ? databaseRestoredRoom.changeName : [], @@ -155,40 +139,10 @@ export function getRoomsBaseDict( }; } -export function convertNumberToSex(m: SexDB): SexModelString { - const newVar: Record = { - 0: "Secret", - 1: "Male", - 2: "Female", - }; - - return newVar[m]; -} - -export function convertSexToString(m: SexDB): SexModelString { - const newVar: Record = { - 0: "Secret", - 1: "Male", - 2: "Female", - }; - - return newVar[m]; -} - export function convertToBoolean(value: BooleanDB): boolean { return value === 1; } -export function convertStringSexToNumber(m: SexModelString): SexDB { - const newVar: Record = { - Secret: 0, - Male: 1, - Female: 2, - }; - return newVar[m]; -} - - export function messageModelToP2p(m: MessageModel): MessageP2pDto { return { content: m.content, @@ -248,9 +202,9 @@ export function convertMessageModelDtoToModel(message: MessageModelDto, oldMessa } export function p2pMessageToModel(m: MessageP2pDto, roomId: number): MessageModel { - let status: MessageStatus = "received"; - if (m.status === "read") { - status = "read"; + let status: MessageStatus = MessageStatus.RECEIVED; + if (m.status === MessageStatus.READ) { + status = MessageStatus.READ; } return { content: m.content, @@ -275,9 +229,9 @@ export function p2pMessageToModel(m: MessageP2pDto, roomId: number): MessageMode export function convertUser(u: UserDto, location: LocationDto | null): UserModel { return { - user: u.user, - id: u.userId, - image: u.userImage, + username: u.username, + id: u.id, + thumbnail: u.thumbnail, lastTimeOnline: u.lastTimeOnline, sex: convertSex(u.sex), location: convertLocation(location), diff --git a/frontend/src/ts/types/db.ts b/frontend/src/ts/types/db.ts index 3cc801a59..528759024 100644 --- a/frontend/src/ts/types/db.ts +++ b/frontend/src/ts/types/db.ts @@ -1,9 +1,11 @@ +import type {Gender} from "@common/model/enum/gender"; import type {LogLevel} from "lines-logger"; -import type {MessageStatus} from "@/ts/types/model"; +import type {MessageStatusInner} from "@/ts/types/model"; + export interface UserDB { id: number; - user: string; + username: string; sex: SexDB; last_time_online: number; deleted: BooleanDB; @@ -14,10 +16,7 @@ export interface UserDB { city: string; } -export type SexDB = - 0 - | 1 - | 2; +export type SexDB = Gender; export type BooleanDB = 0 | 1; @@ -31,13 +30,13 @@ export interface RoomDB { channel_id: number; is_main_in_channel: BooleanDB; deleted: BooleanDB; - creator: number; + creator_id: number; } export interface ChannelDB { id: number; name: string; - creator: number; + creator_id: number; } export interface TagDB { @@ -58,11 +57,12 @@ export interface MessageDB { edited: number; room_id: number; user_id: number; - status: MessageStatus; + status: MessageStatusInner; } export interface FileDB { id: number; + name: string; preview_file_id: number; // So this is UploadFile.id for "preview" File file_id: number; // So this is UploadFile.id for "url" File server_id: number; // If we saved this file on backend, it would have its id @@ -90,13 +90,13 @@ export interface SettingsDB { export interface ProfileDB { user_id: number; - user: string; + username: string; name: string; city: string; - image: string; + thumbnail: string; surname: string; email: string; - birthday: string; + birthday: Date; contacts: string; sex: SexDB; } diff --git a/frontend/src/ts/types/dto.ts b/frontend/src/ts/types/dto.ts index 86ad58cde..6f6b5c7b4 100644 --- a/frontend/src/ts/types/dto.ts +++ b/frontend/src/ts/types/dto.ts @@ -1,85 +1,18 @@ +import type {UserProfileDto} from "@common/model/dto/user.profile.dto"; import type { - BlobType, ChannelsDictModel, CurrentUserInfoModel, CurrentUserSettingsModel, - MessageStatus, + MessageStatusModel, RoomDictModel, - SexModelString, UserModel, } from "@/ts/types/model"; -import type {LogLevel} from "lines-logger"; -export type SexModelDto = - "Female" - | "Male" - | "Secret"; - - -export interface RoomNoUsersDto { - channelId: number | null; - notifications: boolean; - p2p: boolean; - volume: number; - isMainInChannel: boolean; - roomId: number; - name: string; - roomCreatorId: number; -} - -export interface SessionResponse { - session: string; -} - -export interface OauthSessionResponse extends SessionResponse { - isNewAccount: boolean; - username: string; -} - -export interface RoomDto extends RoomNoUsersDto { - users: number[]; -} export interface ViewUserProfileDto extends UserProfileDto { image: string; } -export type SaveFileResponse = Record; - -export interface ChannelDto { - channelName: string; - channelId: number; - channelCreatorId: number; -} - -export interface UserDto { - user: string; - userId: number; - userImage: string; - lastTimeOnline: number; - sex: SexModelString; -} - -export interface LocationDto { - city: string; - country: string; - countryCode: string; - region: string; -} - -export interface UserSettingsDto { - embeddedYoutube: boolean; - highlightCode: boolean; - incomingFileCallSound: boolean; - messageSound: boolean; - onlineChangeSound: boolean; - sendLogs: boolean; - showWhenITyping: boolean; - suggestions: boolean; - logs: LogLevel; - theme: string; -} - export interface SetStateFromWS { roomsDict: RoomDictModel; channelsDict: ChannelsDictModel; @@ -94,56 +27,11 @@ export interface SetStateFromStorage { allUsersDict: Record; } -export interface UserProfileDtoWoImage { - user: string; - name: string; - city: string; - surname: string; - email: string; - birthday: string; - contacts: string; - sex: SexModelDto; - userId: number; -} - -export interface UserProfileDto extends UserProfileDtoWoImage { - userImage: string; -} - -export interface GiphyDto { - webp: string; - url: string; - symbol: string; -} - export interface OauthStatus { google: boolean; facebook: boolean; } -export interface FileModelDto { - url: string; - id: number; - type: BlobType; - preview: string; -} - -export interface MessageModelDto { - id: number; - time: number; - parentMessage: number; - files?: Record; - tags: Record; - content: string; - status: MessageStatus; - symbol?: string; - deleted?: boolean; - threadMessagesCount: number; - edited: number; - roomId: number; - userId: number; -} - -export interface WebRtcMessageModelDto extends Omit { +export interface WebRtcMessageModelDto extends Omit { timeDiff: number; } diff --git a/frontend/src/ts/types/env.d.ts b/frontend/src/ts/types/env.d.ts index 15268aa17..cf5a0df48 100644 --- a/frontend/src/ts/types/env.d.ts +++ b/frontend/src/ts/types/env.d.ts @@ -1,19 +1,12 @@ -import type WsHandler from "@/ts/message_handlers/WsHandler"; -import type Api from "@/ts/message_handlers/Api"; +import type WsApi from "@/ts/message_handlers/WsApi"; +import type HttpApi from "@/ts/message_handlers/HttpApi"; import type {Logger} from "lines-logger"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type { - GoogleCaptcha, - PlatformUtil, -} from "@/ts/types/model"; +import type {GoogleCaptcha, PlatformUtil} from "@/ts/types/model"; import type WsMessageHandler from "@/ts/message_handlers/WsMessageHandler"; -import type { - IStorage, - JsAudioAnalyzer, -} from "@/ts/types/types"; +import type {IStorage, JsAudioAnalyzer} from "@/ts/types/types"; import type WebRtcApi from "@/ts/webrtc/WebRtcApi"; import type Subscription from "@/ts/classes/Subscription"; -import type Http from "@/ts/classes/Http"; import type {Router} from "vue-router"; import type {MessageSenderProxy} from "@/ts/message_handlers/MessageSenderProxy"; import type {RoomHandler} from "@/ts/message_handlers/RomHandler"; @@ -24,13 +17,13 @@ declare global { interface Window { GIT_VERSION: string | undefined; vue: VueApp; + onloadrecaptcha(): any; router: Router; - api: Api; + api: HttpApi; deferredPrompt: BeforeInstallPromptEvent; wsMessageHandler: WsMessageHandler; roomHandler: RoomHandler; - xhr: Http; - ws: WsHandler; + ws: WsApi; storage: IStorage; store: DefaultStore; webrtcApi: WebRtcApi; @@ -45,8 +38,8 @@ declare global { declare module "@vue/runtime-core" { export interface ComponentCustomProperties { - $ws: WsHandler; - $api: Api; + $ws: WsApi; + $api: HttpApi; $store: DefaultStore; $logger: Logger; $noVerbose?: true; diff --git a/frontend/src/ts/types/messages/baseMessagesInterfaces.ts b/frontend/src/ts/types/messages/baseMessagesInterfaces.ts deleted file mode 100644 index 05eab9623..000000000 --- a/frontend/src/ts/types/messages/baseMessagesInterfaces.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * This file should only contain interfaces that is used in this package (messages) by other interfaces - */ -import type { - ChannelDto, - RoomDto, -} from "@/ts/types/dto"; - - -export interface ChangeUserOnlineBase { - content: Record; - userId: number; - lastTimeOnline: number; - time: number; -} - -/* - * Any means that every every registered subscriber will be called with this handler if it exists - * this means, that handler that registered this event will be called - * void means that no handlers should process this signal - */ -export type HandlerName = - "*" - | "call" - | "notifier" - | "peerConnection:*" - | "room" - | "router" - | "void" - | "webrtc-message" - | "webrtc" - | "webrtcTransfer:*" - | "ws-message" - | "ws"; -export type CallHandlerName = - HandlerName - | "dummyCall"; - -export type HandlerType = (a: DefaultInMessage) => Promise | void; - -export type HandlerTypes = { - [Key in K]?: HandlerType -}; - -export interface AcceptFileContent { - received: number; -} - -export interface ReplyWebRtc extends WebRtcDefaultMessage, OpponentWsId { - content: BrowserBase; - userId: number; -} - -export interface NewRoom { - inviterUserId: number; - time: number; -} - -export interface RoomExistedBefore { - inviteeUserId: number[]; -} - -export interface AddRoomBase extends NewRoom, Omit, RoomDto { -} - -export interface OpponentWsId { - opponentWsId: string; -} - -export interface WebRtcDefaultMessage { - connId: string; -} - -export interface OfferFileContent extends BrowserBase { - size: number; - name: string; -} - -export type ChangeDeviceType = - "i_deleted" - | "invited" - | "room_created" - | "someone_joined" - | "someone_left"; -export type ChangeOnlineType = - "appear_online" - | "gone_offline"; - -export interface BrowserBase { - browser: string; -} - -export interface CallBackMessage { - // Request to send response with this callback id - cbId?: number; -} - -export interface DefaultMessage { - action: A; -} - -export interface DefaultInMessage extends DefaultMessage { - handler: H; -} - -export interface ResolveCallbackId { - resolveCbId?: number; // If this callback id is present, resolve it -} - -export interface IMessageHandler { - handle(message: DefaultInMessage): void; - - getHandler(message: DefaultInMessage): HandlerType | undefined; -} - -export type CallStatus = - "accepted" - | "not_inited" - | "received_offer" - | "sent_offer"; diff --git a/frontend/src/ts/types/messages/helper.ts b/frontend/src/ts/types/messages/helper.ts new file mode 100644 index 000000000..7487bc1f1 --- /dev/null +++ b/frontend/src/ts/types/messages/helper.ts @@ -0,0 +1,6 @@ +import type {DefaultWsInMessage, HandlerName} from "@common/ws/common"; + +export interface DefaultInnerSystemMessage extends DefaultWsInMessage { + // If true, no errors should be present on handeling this message by sucrcription if nothing was notified + allowZeroSubscribers?: boolean; +} diff --git a/frontend/src/ts/types/messages/inner/answer.call.ts b/frontend/src/ts/types/messages/inner/answer.call.ts new file mode 100644 index 000000000..813543505 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/answer.call.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type AnswerCallMessage = DefaultWsInMessage<"answerCall", "webrtcTransfer:*", null>; diff --git a/frontend/src/ts/types/messages/inner/change.p2p.room.info.ts b/frontend/src/ts/types/messages/inner/change.p2p.room.info.ts new file mode 100644 index 000000000..2f19a0ad5 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/change.p2p.room.info.ts @@ -0,0 +1,13 @@ +import type {ChangeDeviceType} from "@common/model/ws.base"; + + +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + + +export interface ChangeP2pRoomInfoMessageBody { + allowZeroSubscribers: true; + changeType: ChangeDeviceType; + roomId: number; + userId: number | null; +} +export type ChangeP2pRoomInfoMessage = DefaultInnerSystemMessage<"changeDevices", "webrtc", ChangeP2pRoomInfoMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/change.stream.ts b/frontend/src/ts/types/messages/inner/change.stream.ts new file mode 100644 index 000000000..86a8b6efd --- /dev/null +++ b/frontend/src/ts/types/messages/inner/change.stream.ts @@ -0,0 +1,6 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface ChangeStreamMessageBody { + newStream: MediaStream; +} +export type ChangeStreamMessage = DefaultInnerSystemMessage<"streamChanged", "peerConnection:*", ChangeStreamMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/change.user.online.info.ts b/frontend/src/ts/types/messages/inner/change.user.online.info.ts new file mode 100644 index 000000000..2ba84f17d --- /dev/null +++ b/frontend/src/ts/types/messages/inner/change.user.online.info.ts @@ -0,0 +1,12 @@ +import type {ChangeOnlineType} from "@common/model/ws.base"; + + +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface ChangeOnlineBody { + opponentWsId: string; + userId: number; + changeType: ChangeOnlineType; +} + +export type ChangeOnlineMessage = DefaultInnerSystemMessage<"changeOnline", "webrtc", ChangeOnlineBody>; diff --git a/frontend/src/ts/types/messages/inner/check.transfer.destroy.ts b/frontend/src/ts/types/messages/inner/check.transfer.destroy.ts new file mode 100644 index 000000000..ff4e8ce38 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/check.transfer.destroy.ts @@ -0,0 +1,7 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface CheckTransferDestroyBody { + wsOpponentId: string; +} +export type CheckTransferDestroyMessage = DefaultInnerSystemMessage<"checkTransferDestroy", "webrtcTransfer:*", CheckTransferDestroyBody>; + diff --git a/frontend/src/ts/types/messages/inner/connect.to.remote.ts b/frontend/src/ts/types/messages/inner/connect.to.remote.ts new file mode 100644 index 000000000..d0b6597dc --- /dev/null +++ b/frontend/src/ts/types/messages/inner/connect.to.remote.ts @@ -0,0 +1,7 @@ +import type {HandlerName} from "@common/ws/common"; +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface ConnectToRemoteMessageBody { + stream: MediaStream | null; +} +export type ConnectToRemoteMessage = DefaultInnerSystemMessage<"connectToRemote", HandlerName, ConnectToRemoteMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/decline.call.ts b/frontend/src/ts/types/messages/inner/decline.call.ts new file mode 100644 index 000000000..d95c0ec3b --- /dev/null +++ b/frontend/src/ts/types/messages/inner/decline.call.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type DeclineCallMessage = DefaultWsInMessage<"declineCall", "webrtcTransfer:*", null>; diff --git a/frontend/src/ts/types/messages/inner/destroy.peer.connection.ts b/frontend/src/ts/types/messages/inner/destroy.peer.connection.ts new file mode 100644 index 000000000..72efd98e0 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/destroy.peer.connection.ts @@ -0,0 +1,3 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export type DestroyPeerConnectionMessage = DefaultInnerSystemMessage<"destroy", "peerConnection:*", null>; diff --git a/frontend/src/ts/types/messages/inner/internet.appear.ts b/frontend/src/ts/types/messages/inner/internet.appear.ts new file mode 100644 index 000000000..a17a73804 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/internet.appear.ts @@ -0,0 +1,3 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export type InternetAppearMessage = DefaultInnerSystemMessage<"internetAppear", "*", null>; diff --git a/frontend/src/ts/types/messages/inner/login.ts b/frontend/src/ts/types/messages/inner/login.ts new file mode 100644 index 000000000..f1aca348f --- /dev/null +++ b/frontend/src/ts/types/messages/inner/login.ts @@ -0,0 +1,7 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface LoginMessageBody { + session: string; +} + +export type LoginMessage = DefaultInnerSystemMessage<"login", "router", LoginMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/logout.ts b/frontend/src/ts/types/messages/inner/logout.ts new file mode 100644 index 000000000..8020c04a3 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/logout.ts @@ -0,0 +1,3 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export type LogoutMessage = DefaultInnerSystemMessage<"logout", "*", null>; diff --git a/frontend/src/ts/types/messages/inner/pub.set.rooms.ts b/frontend/src/ts/types/messages/inner/pub.set.rooms.ts new file mode 100644 index 000000000..77d5da0c6 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/pub.set.rooms.ts @@ -0,0 +1,16 @@ +import type {ChannelDto} from "@common/model/dto/channel.dto"; +import type {RoomDto} from "@common/model/dto/room.dto"; +import type {UserDto} from "@common/model/dto/user.dto"; + + +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + + +export interface PubSetRoomsMessageBody { + rooms: RoomDto[]; + channels: ChannelDto[]; + users: UserDto[]; + online: Record; +} + +export type PubSetRoomsMessage = DefaultInnerSystemMessage<"init", "room", PubSetRoomsMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/router.navigate.ts b/frontend/src/ts/types/messages/inner/router.navigate.ts new file mode 100644 index 000000000..c81fc7c2c --- /dev/null +++ b/frontend/src/ts/types/messages/inner/router.navigate.ts @@ -0,0 +1,7 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface RouterNavigateMessageBody { + to: string; +} + +export type RouterNavigateMessage = DefaultInnerSystemMessage<"navigate", "router", RouterNavigateMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/scroll.ts b/frontend/src/ts/types/messages/inner/scroll.ts new file mode 100644 index 000000000..3d51616bd --- /dev/null +++ b/frontend/src/ts/types/messages/inner/scroll.ts @@ -0,0 +1,3 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export type ScrollInnerSystemMessage = DefaultInnerSystemMessage<"scroll", "*", null>; diff --git a/frontend/src/ts/types/messages/inner/send.set.message.status.ts b/frontend/src/ts/types/messages/inner/send.set.message.status.ts new file mode 100644 index 000000000..05a9b6e8e --- /dev/null +++ b/frontend/src/ts/types/messages/inner/send.set.message.status.ts @@ -0,0 +1,11 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; +import type {MessageStatus} from "@common/model/enum/message.status"; + +export interface SendSetMessagesStatusInnerSystemBody { + messageIds: number[]; + status: MessageStatus; +} + +export interface SendSetMessagesStatusInnerSystemMessage extends DefaultInnerSystemMessage<"sendSetMessagesStatus", "peerConnection:*", SendSetMessagesStatusInnerSystemBody> { + allowZeroSubscribers: true; +} diff --git a/frontend/src/ts/types/messages/inner/send.set.messages.status.ts b/frontend/src/ts/types/messages/inner/send.set.messages.status.ts new file mode 100644 index 000000000..6f5a01086 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/send.set.messages.status.ts @@ -0,0 +1,8 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; +import type {MessageStatusModel} from "@/ts/types/model"; + +export interface SendSetMessagesStatusMessageBody { + messageIds: number[]; + status: MessageStatusModel; +} +export type SendSetMessagesStatusMessage = DefaultInnerSystemMessage<"sendSetMessagesStatus", "peerConnection:*", SendSetMessagesStatusMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/sync.p2p.ts b/frontend/src/ts/types/messages/inner/sync.p2p.ts new file mode 100644 index 000000000..a8e9d2717 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/sync.p2p.ts @@ -0,0 +1,6 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export interface SyncP2PMessageBody { + id: number; +} +export type SyncP2PInnerSystemMessage = DefaultInnerSystemMessage<"syncP2pMessage", "peerConnection:*", SyncP2PMessageBody>; diff --git a/frontend/src/ts/types/messages/inner/video.answer.call.ts b/frontend/src/ts/types/messages/inner/video.answer.call.ts new file mode 100644 index 000000000..164ac6a28 --- /dev/null +++ b/frontend/src/ts/types/messages/inner/video.answer.call.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type VideoAnswerCallMessage = DefaultWsInMessage<"videoAnswerCall", "webrtcTransfer:*", null>; diff --git a/frontend/src/ts/types/messages/innerMessages.ts b/frontend/src/ts/types/messages/innerMessages.ts deleted file mode 100644 index 451793c1e..000000000 --- a/frontend/src/ts/types/messages/innerMessages.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * This file should only contain interfaces is used to create Messages to notify. - * So if you're creating structure that you pass to sub.notify() it should go here. - * This excludes messages that comes deserialized from websocket or any other network - */ -import type { - ChannelDto, - RoomDto, - UserDto, -} from "@/ts/types/dto"; -import type { - ChangeDeviceType, - ChangeOnlineType, - DefaultInMessage, - HandlerName, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {MessageStatus} from "@/ts/types/model"; - - -export interface DefaultInnerSystemMessage extends DefaultInMessage { - allowZeroSubscribers?: boolean; // If true, no errors should be present on handeling this message by sucrcription if nothing was notified -} - -export interface PubSetRooms extends DefaultInnerSystemMessage<"init", "room"> { - rooms: RoomDto[]; - channels: ChannelDto[]; - users: UserDto[]; - online: Record; -} - -export type InternetAppearMessage = DefaultInnerSystemMessage<"internetAppear", "*">; - -export interface LoginMessage extends DefaultInnerSystemMessage<"login", "router"> { - session: string; -} - -export type LogoutMessage = DefaultInnerSystemMessage<"logout", "*">; - -export interface RouterNavigateMessage extends DefaultInnerSystemMessage<"navigate", "router"> { - to: string; -} - -export interface ChangeUserOnlineInfoMessage extends DefaultInnerSystemMessage<"changeOnline", "webrtc"> { - opponentWsId: string; - userId: number; - changeType: ChangeOnlineType; -} - -export interface ChangeP2pRoomInfoMessage extends DefaultInnerSystemMessage<"changeDevices", "webrtc"> { - allowZeroSubscribers: true; - changeType: ChangeDeviceType; - roomId: number; - userId: number | null; -} - -export interface ConnectToRemoteMessage extends DefaultInnerSystemMessage<"connectToRemote", HandlerName> { - stream: MediaStream | null; -} - -export interface CheckTransferDestroy extends DefaultInnerSystemMessage<"checkTransferDestroy", "webrtcTransfer:*"> { - wsOpponentId: string; -} - -export interface ChangeStreamMessage extends DefaultInnerSystemMessage<"streamChanged", "peerConnection:*"> { - newStream: MediaStream; -} - -export type DestroyPeerConnectionMessage = DefaultInnerSystemMessage<"destroy", "peerConnection:*">; - -export interface SyncP2PMessage extends DefaultInnerSystemMessage<"syncP2pMessage", "peerConnection:*"> { - id: number; -} - -export interface SendSetMessagesStatusMessage extends DefaultInnerSystemMessage<"sendSetMessagesStatus", "peerConnection:*"> { - messageIds: number[]; - status: MessageStatus; -} diff --git a/frontend/src/ts/types/messages/p2p/confirm.message.status.ts b/frontend/src/ts/types/messages/p2p/confirm.message.status.ts new file mode 100644 index 000000000..6b1216825 --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/confirm.message.status.ts @@ -0,0 +1,3 @@ +import {DefaultP2pMessage} from "@/ts/types/messages/p2p"; + +export type ConfirmSetMessageStatusP2pMessage = DefaultP2pMessage<"confirmSetMessageStatusRequest", null>; diff --git a/frontend/src/ts/types/messages/p2p/dto/file.ts b/frontend/src/ts/types/messages/p2p/dto/file.ts new file mode 100644 index 000000000..551f95a2b --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/dto/file.ts @@ -0,0 +1,4 @@ +export interface FileP2pDto { + base64: string; + name: string; +} diff --git a/frontend/src/ts/types/messages/p2p/dto/message.info.ts b/frontend/src/ts/types/messages/p2p/dto/message.info.ts new file mode 100644 index 000000000..42a584967 --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/dto/message.info.ts @@ -0,0 +1 @@ +export type MessagesInfo = Record; diff --git a/frontend/src/ts/types/messages/p2p/dto/message.ts b/frontend/src/ts/types/messages/p2p/dto/message.ts new file mode 100644 index 000000000..ee53b4fbd --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/dto/message.ts @@ -0,0 +1,17 @@ +import type {MessageStatusModel} from "@/ts/types/model"; +import type {FileP2pDto} from "@/ts/types/messages/p2p/dto/file"; + +export interface MessageP2pDto { + id: number; + content: string | null; + files: Record; + timeAgo: number; + symbol: string | null; + status: MessageStatusModel; + parentMessage: number | null; + deleted: boolean; + edited: number; + userId: number; +} + + diff --git a/frontend/src/ts/types/messages/p2p/exchange.message.info.ts b/frontend/src/ts/types/messages/p2p/exchange.message.info.ts new file mode 100644 index 000000000..c7333f938 --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/exchange.message.info.ts @@ -0,0 +1,24 @@ +import {MessageStatus} from "@common/model/enum/message.status"; +import { + DefaultP2pMessage, + DefaultRequestP2pMessage, + DefaultRequestResponseP2pMessage +} from "@/ts/types/messages/p2p"; +import {MessagesInfo} from "@/ts/types/messages/p2pDto"; + +export interface SetMessageStatusP2pBody { + status: MessageStatus; + messagesIds: number[]; +} + +export interface ExchangeMessageInfo1P2pBody { + messagesInfo: MessagesInfo; +} + +export interface ExchangeMessageInfo2P2pBody { + messagesInfo: MessagesInfo; +} + +export type ExchangeMessageInfo1RequestP2pMessage = DefaultRequestP2pMessage<"exchangeMessageInfo", ExchangeMessageInfo1P2pBody>; + +export type ExchangeMessageInfo2 = DefaultRequestResponseP2pMessage<>; diff --git a/frontend/src/ts/types/messages/p2p/index.ts b/frontend/src/ts/types/messages/p2p/index.ts new file mode 100644 index 000000000..2951024f9 --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/index.ts @@ -0,0 +1,19 @@ +export interface DefaultP2pMessage { + action: A; + data: D; +} + +export interface DefaultRequestP2pMessage extends DefaultP2pMessage { + cbId: number; +} + +export interface DefaultResponseP2pMessage { + data: D; + resolveCbId: number; +} + +export interface DefaultRequestResponseP2pMessage { + data: D; + resolveCbId: number; + cbId: number; +} diff --git a/frontend/src/ts/types/messages/p2p/set.message.status.ts b/frontend/src/ts/types/messages/p2p/set.message.status.ts new file mode 100644 index 000000000..07a3ccf2b --- /dev/null +++ b/frontend/src/ts/types/messages/p2p/set.message.status.ts @@ -0,0 +1,8 @@ +import {MessageStatus} from "@common/model/enum/message.status"; +import {DefaultRequestP2pMessage} from "@/ts/types/messages/p2p"; + +export interface SetMessageStatusP2pBody { + status: MessageStatus; + messagesIds: number[]; +} +export type SetMessageStatusP2pMessage = DefaultRequestP2pMessage<"setMessageStatus", SetMessageStatusP2pBody>; diff --git a/frontend/src/ts/types/messages/p2pDto.ts b/frontend/src/ts/types/messages/p2pDto.ts index eacc588f1..1a0447741 100644 --- a/frontend/src/ts/types/messages/p2pDto.ts +++ b/frontend/src/ts/types/messages/p2pDto.ts @@ -1,21 +1,4 @@ -import type {MessageStatus} from "@/ts/types/model"; +import type {MessageStatusModel} from "@/ts/types/model"; -export interface MessageP2pDto { - id: number; - content: string | null; - files: Record; - timeAgo: number; - symbol: string | null; - status: MessageStatus; - parentMessage: number | null; - deleted: boolean; - edited: number; - userId: number; -} -export interface FileP2pDto { - base64: string; - name: string; -} -export type MessagesInfo = Record; diff --git a/frontend/src/ts/types/messages/p2pMessages.ts b/frontend/src/ts/types/messages/p2pMessages.ts index a13884a32..ac902d914 100644 --- a/frontend/src/ts/types/messages/p2pMessages.ts +++ b/frontend/src/ts/types/messages/p2pMessages.ts @@ -3,38 +3,7 @@ * So if we create a structure on one PC (on frontend) and handle on another (on frontend as well) * this file should do it */ -import type { - CallBackMessage, - DefaultMessage, - ResolveCallbackId, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - MessageP2pDto, - MessagesInfo, -} from "@/ts/types/messages/p2pDto"; -import type {MessageStatus} from "@/ts/types/model"; - - -export type P2PHandlerType = (a: DefaultP2pMessage) => Promise | void; - -export type P2PHandlerTypes = { - [Key in K]?: P2PHandlerType -}; - -export interface DefaultP2pMessage extends DefaultMessage, ResolveCallbackId { - -} - - -export interface ExchangeMessageInfoResponse3 extends DefaultP2pMessage<"exchangeMessageInfoResponse3">, CallBackMessage { -} - -export interface SetMessageStatusRequest extends DefaultP2pMessage<"setMessageStatus">, CallBackMessage { - status: MessageStatus; - messagesIds: number[]; -} - -export type ConfirmSetMessageStatusRequest = DefaultP2pMessage<"confirmSetMessageStatusRequest">; +import type {MessageStatus} from "@common/model/enum/message.status"; export interface ExchangeMessageInfoResponse extends DefaultP2pMessage<"exchangeMessageInfoResponse">, CallBackMessage { messages: MessageP2pDto[]; @@ -56,3 +25,6 @@ export interface SendNewP2PMessage extends DefaultP2pMessage<"sendNewP2PMessage" export interface ConfirmReceivedP2pMessage extends DefaultP2pMessage<"confirmReceivedP2pMessage"> { status?: MessageStatus; } + +export interface ExchangeMessageInfoResponse3 extends DefaultP2pMessage<"exchangeMessageInfoResponse3">, CallBackMessage { +} diff --git a/frontend/src/ts/types/messages/peer-connection/accept.file.reply.ts b/frontend/src/ts/types/messages/peer-connection/accept.file.reply.ts new file mode 100644 index 000000000..fd47c3fe9 --- /dev/null +++ b/frontend/src/ts/types/messages/peer-connection/accept.file.reply.ts @@ -0,0 +1,3 @@ +import type {DefaultInnerSystemMessage} from "@/ts/types/messages/helper"; + +export type AcceptFileReplyInnerSystemMessage = DefaultInnerSystemMessage<"acceptFileReply", "peerConnection:*", null>; diff --git a/frontend/src/ts/types/messages/peer-connection/check.destroy.ts b/frontend/src/ts/types/messages/peer-connection/check.destroy.ts new file mode 100644 index 000000000..f27cf6765 --- /dev/null +++ b/frontend/src/ts/types/messages/peer-connection/check.destroy.ts @@ -0,0 +1,3 @@ +import {DeclineSendingMessage} from "@/ts/types/messages/peer-connection/decline.sending"; + +DeclineSendingMessage diff --git a/frontend/src/ts/types/messages/peer-connection/decline.file.reply.ts b/frontend/src/ts/types/messages/peer-connection/decline.file.reply.ts new file mode 100644 index 000000000..c5c1d8944 --- /dev/null +++ b/frontend/src/ts/types/messages/peer-connection/decline.file.reply.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type DeclineFileReply = DefaultWsInMessage<"declineFileReply", "peerConnection:*", null>; diff --git a/frontend/src/ts/types/messages/peer-connection/decline.sending.ts b/frontend/src/ts/types/messages/peer-connection/decline.sending.ts new file mode 100644 index 000000000..d568df395 --- /dev/null +++ b/frontend/src/ts/types/messages/peer-connection/decline.sending.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type DeclineSendingMessage = DefaultWsInMessage<"declineSending", "peerConnection:*", null>; diff --git a/frontend/src/ts/types/messages/peer-connection/retry.file.reply.ts b/frontend/src/ts/types/messages/peer-connection/retry.file.reply.ts new file mode 100644 index 000000000..172472aa9 --- /dev/null +++ b/frontend/src/ts/types/messages/peer-connection/retry.file.reply.ts @@ -0,0 +1,3 @@ +import type {DefaultWsInMessage} from "@common/ws/common"; + +export type RetryFileReply = DefaultWsInMessage<"retryFileReply", "peerConnection:*", null>; diff --git a/frontend/src/ts/types/messages/wsInMessages.ts b/frontend/src/ts/types/messages/wsInMessages.ts deleted file mode 100644 index 4df25f61e..000000000 --- a/frontend/src/ts/types/messages/wsInMessages.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** - * This file should only contain structures created by server. Only BE -> FE by websockets - * and processed by MessageHandler - */ - -import type { - ChannelDto, - LocationDto, - MessageModelDto, - RoomDto, - RoomNoUsersDto, - UserDto, - UserProfileDto, - UserProfileDtoWoImage, - UserSettingsDto, -} from "@/ts/types/dto"; - -import type { - AcceptFileContent, - AddRoomBase, - BrowserBase, - CallBackMessage, - ChangeUserOnlineBase, - DefaultInMessage, - HandlerName, - NewRoom, - OfferFileContent, - OpponentWsId, - ReplyWebRtc, - RoomExistedBefore, - WebRtcDefaultMessage, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {MessageStatus} from "@/ts/types/model"; - -export interface DefaultWsInMessage extends DefaultInMessage, CallBackMessage { - cbBySender?: string; - cbId?: number; -} - -export interface MessagesResponseMessage { - content: MessageModelDto[]; -} - -export interface SyncHistoryResponseMessage extends MessagesResponseMessage { - readMessageIds: number[]; - receivedMessageIds: number[]; -} - -export interface DeleteMessage extends DefaultWsInMessage<"deleteMessage", "ws-message"> { - roomId: number; - id: number; - edited: number; -} - -export interface SetMessageStatusMessage extends DefaultWsInMessage<"setMessageStatus", "ws-message"> { - roomId: number; - status: MessageStatus; - messagesIds: number[]; -} - -export interface EditMessage extends DefaultWsInMessage<"editMessage", "ws-message">, MessageModelDto { -} - -export interface PrintMessage extends DefaultWsInMessage<"printMessage", "ws-message">, MessageModelDto { -} - -export interface AddOnlineUserMessage extends DefaultWsInMessage<"addOnlineUser", "room">, ChangeUserOnlineBase { - opponentWsId: string; -} - -export interface CreateNewUsedMessage extends DefaultWsInMessage<"createNewUser", "room">, UserDto { - rooms: { - roomId: number; - users: number[]; - }[]; -} - -export interface RemoveOnlineUserMessage extends DefaultWsInMessage<"removeOnlineUser", "room">, ChangeUserOnlineBase { -} - -export interface DeleteRoomMessage extends DefaultWsInMessage<"deleteRoom", "room"> { - roomId: number; -} - -export interface GetCountryCodeMessage extends DefaultInMessage<"getCountryCode", "void"> { - content: Record; -} - -export interface LeaveUserMessage extends DefaultWsInMessage<"leaveUser", "room"> { - roomId: number; - userId: number; - users: number[]; -} - -export interface AddChannelMessage extends DefaultWsInMessage<"addChannel", "room">, ChannelDto, Omit, NewRoom { - channelUsers: number[]; -} - -export interface InviteUserMessage extends NewRoom, RoomExistedBefore, DefaultWsInMessage<"inviteUser", "room"> { - roomId: number; - users: number[]; -} - -export interface AddInviteMessage extends AddRoomBase, RoomExistedBefore, DefaultWsInMessage<"addInvite", "room"> { -} - -export interface SaveChannelSettingsMessage extends DefaultWsInMessage<"saveChannelSettings", "room">, ChannelDto { - notifications: boolean; - volume: number; - p2p: boolean; - userId: number; -} - -export interface DeleteChannelMessage extends DefaultWsInMessage<"deleteChannel", "room"> { - channelId: number; - roomIds: number[]; -} - -export interface SaveRoomSettingsMessage extends DefaultWsInMessage<"saveRoomSettings", "room">, RoomNoUsersDto { -} - -export interface ShowITypeMessage extends DefaultWsInMessage<"showIType", "room"> { - roomId: number; - userId: number; -} - -export interface SetWsIdMessage extends DefaultWsInMessage<"setWsId", "ws">, OpponentWsId { - rooms: RoomDto[]; - channels: ChannelDto[]; - users: UserDto[]; - online: Record; - time: number; - userInfo: UserProfileDto; - userSettings: UserSettingsDto; -} - -export interface WebRtcSetConnectionIdMessage extends WebRtcDefaultMessage, DefaultWsInMessage<"setConnectionId", "void"> { - time: number; -} - -export interface AddRoomMessage extends AddRoomBase, DefaultWsInMessage<"addRoom", "room"> { - channelUsers: number[]; - channelId: number; -} - -export interface OfferFile extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"offerFile", "webrtc"> { - content: OfferFileContent; - roomId: number; - threadId: number | null; - userId: number; - time: number; -} - -export interface OfferCall extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"offerCall", "webrtc"> { - content: BrowserBase; - roomId: number; - userId: number; - time: number; -} - -export interface ReplyCallMessage extends ReplyWebRtc, DefaultWsInMessage<"replyCall", "webrtcTransfer:*"> { - -} - -export interface NotifyCallActiveMessage extends DefaultWsInMessage<"notifyCallActive", "webrtc">, WebRtcDefaultMessage, OpponentWsId { - roomId: number; - userId: number; -} - -export interface DestroyCallConnection extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"destroyCallConnection", "peerConnection:*"> { - content: string; -} - -export interface OfferMessage extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"offerMessage", "webrtc"> { - content: BrowserBase; - roomId: number; - userId: number; - time: number; -} - -export interface SetSettingsMessage extends DefaultWsInMessage<"setSettings", "ws"> { - content: UserSettingsDto; -} - -export interface SetUserProfileMessage extends DefaultWsInMessage<"setUserProfile", "ws"> { - content: UserProfileDtoWoImage; -} - -export interface UserProfileChangedMessage extends DefaultWsInMessage<"userProfileChanged", "ws">, UserDto { - -} - -export interface GrowlMessage { - content: string; - action: "growlError"; -} - -export interface PingMessage extends DefaultWsInMessage<"ping", "ws"> { - time: string; -} - -export interface PongMessage extends DefaultWsInMessage<"pong", "ws"> { - time: string; -} - -export interface ReplyFileMessage extends ReplyWebRtc, DefaultWsInMessage<"replyFile", "webrtcTransfer:*"> { - -} - -export interface SetProfileImageMessage extends DefaultWsInMessage<"setProfileImage", "ws"> { - content: string; -} - -export interface AcceptCallMessage extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"acceptCall", "webrtcTransfer:*"> { -} - -export interface AcceptFileMessage extends DefaultWsInMessage<"acceptFile", "peerConnection:*"> { - content: AcceptFileContent; -} - -export interface SendRtcDataMessage extends WebRtcDefaultMessage, OpponentWsId, DefaultWsInMessage<"sendRtcData", "peerConnection:*"> { - content: RTCIceCandidateInit | RTCSessionDescriptionInit | {message: unknown}; -} - -export type RetryFileMessage = DefaultWsInMessage<"retryFile", "peerConnection:*">; - -export interface DestroyFileConnectionMessage extends DefaultWsInMessage<"destroyFileConnection", "peerConnection:*"> { - content: "decline" | "success"; -} diff --git a/frontend/src/ts/types/messages/wsOutMessages.ts b/frontend/src/ts/types/messages/wsOutMessages.ts deleted file mode 100644 index ea6c3d297..000000000 --- a/frontend/src/ts/types/messages/wsOutMessages.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This file only contains messages that sent to backend api. FE -> BE by websockets - */ - -import type {DefaultMessage} from "@/ts/types/messages/baseMessagesInterfaces"; - -export interface DefaultWsOutMessage extends DefaultMessage { - cbId?: number; -} - -export interface SyncHistoryOutMessage extends DefaultWsOutMessage<"syncHistory"> { - roomIds: number[]; - messagesIds: number[]; - onServerMessageIds: number[]; - receivedMessageIds: number[]; - lastSynced: number; -} diff --git a/frontend/src/ts/types/model.ts b/frontend/src/ts/types/model.ts index b2dabbaac..7b5447089 100644 --- a/frontend/src/ts/types/model.ts +++ b/frontend/src/ts/types/model.ts @@ -1,9 +1,17 @@ +import type {Gender} from "@common/model/enum/gender"; +import type {ImageType} from "@common/model/enum/image.type"; +import type {MessageStatus} from "@common/model/enum/message.status"; import type {LogLevel} from "lines-logger"; + export enum GrowlType { SUCCESS = "col-success", INFO = "col-info", ERROR = "col-error", } +export enum WsState { + NOT_INITED, TRIED_TO_CONNECT, CONNECTION_IS_LOST, CONNECTED, +} + export interface GrowlModel { id: number; html: string; @@ -29,7 +37,6 @@ export interface CurrentUserSettingsModel { incomingFileCallSound: boolean; messageSound: boolean; onlineChangeSound: boolean; - sendLogs: boolean; showWhenITyping: boolean; suggestions: boolean; theme: string; @@ -37,7 +44,7 @@ export interface CurrentUserSettingsModel { } export interface GoogleCaptcha { - render(div: HTMLElement): void; + render(div: HTMLElement, options: any): void; reset(): void; } @@ -51,31 +58,27 @@ export interface PastingTextAreaElement { } export interface CurrentUserInfoModel extends CurrentUserInfoWoImage { - image: string | null; + thumbnail: string | null; } export interface CurrentUserInfoWoImage { - userId: number; - user: string; + id: number; + username: string; name: string; city: string; surname: string; email: string; - birthday: string; + birthday: Date; contacts: string; - sex: SexModelString; + sex: Gender; } -export type SexModelString = - "Female" - | "Male" - | "Secret"; export interface UserModel { - user: string; + username: string; id: number; - sex: SexModelString; - image: string; + sex: Gender; + thumbnail: string; lastTimeOnline: number; location: Location; } @@ -87,25 +90,10 @@ export interface Location { region: string | null; } -/* - * F - file - * g - giphy - * i - image - * v - video - * a - audio - * m - media (same as video, but you need to click on image, in order to load video) - */ -export type BlobType = - "a" - | "f" - | "g" - | "i" - | "m" - | "v"; - export interface FileModel { url: string | null; - type: BlobType; + type: ImageType; + name?: string; // TODO serverId: number | null; previewFileId: number | null; fileId: number | null; @@ -121,15 +109,9 @@ export interface UploadProgressModel { export interface MessageTransferInfo { upload: UploadProgressModel | null; error: string | null; - xhr: XMLHttpRequest | null; + abortFn: (() => void) | null; } -export type MessageStatus = - "on_server" - | "read" - | "received" - | "sending"; - export interface MessageModel { id: number; time: number; @@ -143,13 +125,19 @@ export interface MessageModel { symbol: string | null; threadMessagesCount: number; deleted: boolean; - status: MessageStatus; + status: MessageStatusModel; edited: number; roomId: number; userId: number; transfer: MessageTransferInfo | null; } +export enum MessageStatusInner { + SENDING = "SENDING", +} +export type MessageStatusModel = MessageStatus | MessageStatusInner; + + export interface RoomSettingsModel { id: number; name: string; @@ -158,7 +146,7 @@ export interface RoomSettingsModel { isMainInChannel: boolean; notifications: boolean; volume: number; - creator: number; + creatorId: number; } export type UserDictModel = Record; @@ -253,7 +241,7 @@ export interface ChannelModel { expanded: boolean; id: number; name: string; - creator: number; + creatorId: number; } export interface ChannelUIModel extends ChannelModel { diff --git a/frontend/src/ts/types/types.ts b/frontend/src/ts/types/types.ts index 63822905b..cc28effd1 100644 --- a/frontend/src/ts/types/types.ts +++ b/frontend/src/ts/types/types.ts @@ -1,3 +1,5 @@ +import type {SaveFileResponse} from "@common/http/file/save.file"; +import type {DefaultWsInMessage, HandlerName} from "@common/ws/common"; import type { CallInfoModel, ChannelModel, @@ -6,7 +8,7 @@ import type { FileModel, FileTransferStatus, MessageModel, - MessageStatus, + MessageStatusModel, RoomLog, RoomModel, RoomSettingsModel, @@ -14,16 +16,17 @@ import type { UploadProgressModel, UserModel, } from "@/ts/types/model"; -import type { - SaveFileResponse, - SetStateFromStorage, -} from "@/ts/types/dto"; - +import type {SetStateFromStorage} from "@/ts/types/dto"; +import AbstractPeerConnection from "@/ts/webrtc/AbstractPeerConnection"; +import {DestroyPeerConnectionMessage} from "@/ts/types/messages/inner/destroy.peer.connection"; +import {LoginMessage} from "@/ts/types/messages/inner/login"; +import type {PrintMessageWsInMessage} from "@common/ws/message/ws-message/print.message"; +import type {MessageModelDto} from "@common/model/dto/message.model.dto"; +import { + AddChannelWsInBody, + AddChannelWsInMessage +} from "@common/ws/message/room/add.channel"; -export interface UploadFile { - key: string; - file: Blob | File; -} export type ValueFilterForKey = { [K in keyof T]: U extends T[K] ? K : never; @@ -32,11 +35,6 @@ export type ValueFilterForKey = { type StuctureMappedType = Record; -const sample: StuctureMappedType = { - any3: "andy3", -}; - - export interface UserIdConn { connectionId: string; userId: number; @@ -88,7 +86,7 @@ export interface SetUploadProgress { } export interface SetUploadXHR { - xhr: XMLHttpRequest; + abortFunction(): void; roomId: number; messageId: number; } @@ -162,7 +160,7 @@ export interface SetMessageProgressError { export interface SetFileIdsForMessage { messageId: number; roomId: number; - fileIds: SaveFileResponse; + files: SaveFileResponse[]; } export interface RoomMessageIds { @@ -176,11 +174,6 @@ export interface RoomMessagesIds { roomId: number; } -export interface MessageSupplier { - sendRawTextToServer(message: string): boolean; - - getWsConnectionId(): string; -} export interface IStorage { // GetIds(cb: SingleParamCB); @@ -216,7 +209,7 @@ export interface IStorage { setUsers(users: UserModel[]): void; - setMessagesStatus(messagesIds: number[], status: MessageStatus): void; + setMessagesStatus(messagesIds: number[], status: MessageStatusModel): void; saveUser(users: UserModel): void; @@ -234,24 +227,15 @@ export interface IStorage { export interface PostData { url: string; - params?: Record; - formData?: FormData; - isJsonEncoded?: boolean; - isJsonDecoded?: boolean; - checkOkString?: boolean; - errorDescription?: string; - - process?(R: XMLHttpRequest): void; + params: any; + onSetAbortFunction?(c: () => void): void; } -export interface GetData { +export interface UploadData { url: string; - isJsonDecoded?: boolean; - checkOkString?: boolean; - baseUrl?: string; - skipAuth?: boolean; - - process?(R: XMLHttpRequest): void; + data: Record; + onSetAbortFunction?(e: () => void): void; + onProgress?(i: number): void; } export interface AddSendingFileTransfer { @@ -313,6 +297,14 @@ export interface PrivateRoomsIds { roomUsers: Record; } +export interface IMessageHandler { + handle(message: DefaultWsInMessage): void; + + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + getHandler(message: DefaultWsInMessage): HandlerType | undefined; +} + + export interface SetRoomsUsers { roomId: number; users: number[]; @@ -343,4 +335,5 @@ export enum IconColor { export interface SessionHolder { session: string | null; + wsConnectionId: string|null; } diff --git a/frontend/src/ts/utils/consts.ts b/frontend/src/ts/utils/consts.ts index 7c4ce7259..203d5ae32 100644 --- a/frontend/src/ts/utils/consts.ts +++ b/frontend/src/ts/utils/consts.ts @@ -22,7 +22,6 @@ export const { export const CONNECTION_RETRY_TIME = 5000; export const LOAD_GIPHIES_PER_REQUEST = 12; export const CLIENT_NO_SERVER_PING_CLOSE_TIMEOUT = 313000; -export const RESPONSE_SUCCESS = "ok"; export const LOG_LEVEL_LS = "logLevel"; // 1 day diff --git a/frontend/src/ts/utils/htmlApi.ts b/frontend/src/ts/utils/htmlApi.ts index b7f77ac46..782440fde 100644 --- a/frontend/src/ts/utils/htmlApi.ts +++ b/frontend/src/ts/utils/htmlApi.ts @@ -1,17 +1,7 @@ -import { - IS_DEBUG, - PASTED_GIPHY_CLASS, - PASTED_IMG_CLASS, - USERNAME_REGEX, -} from "@/ts/utils/consts"; +import {ImageType} from "@common/model/enum/image.type"; +import {IS_DEBUG, PASTED_GIPHY_CLASS, PASTED_IMG_CLASS, USERNAME_REGEX} from "@/ts/utils/consts"; import type {MessageDataEncode} from "@/ts/types/types"; -import type { - BlobType, - CurrentUserSettingsModel, - FileModel, - MessageModel, - UserModel, -} from "@/ts/types/model"; +import type {CurrentUserSettingsModel, FileModel, MessageModel, UserModel} from "@/ts/types/model"; import recordIcon from "@/assets/img/audio.svg"; import fileIcon from "@/assets/img/file.svg"; import {getFlag} from "@/ts/utils/flags"; @@ -19,13 +9,12 @@ import videoIcon from "@/assets/img/icon-play-red.svg"; import type {SmileysApi} from "@/ts/utils/smileys"; import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; -import { - MEDIA_API_URL, - webpSupported, -} from "@/ts/utils/runtimeConsts"; +import {MEDIA_API_URL, webpSupported} from "@/ts/utils/runtimeConsts"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; import type {GIFObject} from "giphy-api"; import type Subscription from "@/ts/classes/Subscription"; +import type {ScrollInnerSystemMessage} from "@/ts/types/messages/inner/scroll"; + const tmpCanvasContext: CanvasRenderingContext2D = document.createElement("canvas").getContext("2d")!; // TODO why is it not safe? const yotubeTimeRegex = /(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?(\d)?/; @@ -223,7 +212,7 @@ function encodeTags(html: string, tags: Record | null, store: De html = html.replace(imageUnicodeRegex, (s) => { const v = tags[s]; if (v) { - return `@${store.allUsersDict[v].user}`; + return `@${store.allUsersDict[v].username}`; } return s; // If it's absent in files, it could be also in tags so return it. (don't replace ) @@ -238,17 +227,23 @@ function encodeFiles(html: string, files: Record | null) { html = html.replace(imageUnicodeRegex, (s) => { const v = files[s]; if (v) { - if (v.type === "i") { + if (v.type === ImageType.IMAGE && v.preview) { + return ` + + + + `; + } else if (v.type === ImageType.IMAGE) { return ``; - } else if (v.type === "v" || v.type === "m") { - const className = v.type === "v" ? "video-player" : "video-player video-record"; + } else if (v.type === ImageType.VIDEO || v.type === ImageType.MEDIA_RECORD) { + const className = v.type === ImageType.VIDEO ? "video-player" : "video-player video-record"; return `
`; - } else if (v.type === "a") { + } else if (v.type === ImageType.AUDIO_RECORD) { return ``; - } else if (v.type === "f") { + } else if (v.type === ImageType.FILE) { return ``; - } else if (v.type === "g") { + } else if (v.type === ImageType.GIPHY) { // Giphy api sometimes doesn't contain webp, so it can be null return ``; } @@ -274,7 +269,7 @@ export function createTag(user: UserModel) { const style = document.createElement("style"); style.type = "text/css"; const id = `usertag${getUniqueTagId()}`; - style.innerHTML = ` #${id}:after { content: '@${user.user}'}`; + style.innerHTML = ` #${id}:after { content: '@${user.username}'}`; document.getElementsByTagName("head")[0].appendChild(style); a.id = id; @@ -407,9 +402,10 @@ export function setImageFailEvents(e: HTMLElement, bus: Subscription) { this.className += " failed"; }; img.onload = function() { - bus.notify({ + bus.notify({ action: "scroll", handler: "*", + data: null, }); }; }(r[i])); @@ -501,37 +497,51 @@ export function pasteBlobToContentEditable(blob: Blob, textArea: HTMLElement) { export function pasteBlobVideoToTextArea(file: Blob, textArea: HTMLElement, videoType: string, errCb: Function) { const video = document.createElement("video"); + video.style.opacity = "0"; + video.style.width = "0"; + video.style.height = "0"; + video.style.position = "absolute"; + document.body.appendChild(video); if (video.canPlayType(file.type)) { video.autoplay = false; const src = URL.createObjectURL(file); video.loop = false; - video.addEventListener("loadeddata", () => { + video.addEventListener("loadedmetadata", () => { tmpCanvasContext.canvas.width = video.videoWidth; tmpCanvasContext.canvas.height = video.videoHeight; - tmpCanvasContext.drawImage(video, 0, 0); - tmpCanvasContext.canvas.toBlob( - (blob) => { - const img = document.createElement("img"); - if (!blob) { - logger.error(`Failed to render 1st frame image for file ${file.name}, setting videoIcon instead`)(); - img.src = videoIcon; - } else { - const url = URL.createObjectURL(blob); - savedFiles[url] = blob; - blob.name = ".jpg"; - img.src = url; - } - img.className = PASTED_IMG_CLASS; - img.setAttribute("videoType", videoType); - img.setAttribute("associatedVideo", src); - savedFiles[src] = file; - pasteNodeAtCaret(img, textArea); - }, - "image/jpeg", - 0.95, - ); + logger.log(`Loaded video metadata ${video.videoWidth}x${video.videoHeight}`)(); + setTimeout(() => { + tmpCanvasContext.drawImage(video, 0, 0); + tmpCanvasContext.canvas.toBlob( + (blob) => { + const img = document.createElement("img"); + if (!blob) { + logger.error(`Failed to render 1st frame image for file ${file.name}, setting videoIcon instead`)(); + img.src = videoIcon; + } else { + const url = URL.createObjectURL(blob); + savedFiles[url] = blob; + if (file.name) { + blob.name = `${file.name}.jpg`; + } else { + blob.name = ".jpg"; + } + + img.src = url; + } + img.className = PASTED_IMG_CLASS; + img.setAttribute("videoType", videoType); + img.setAttribute("associatedVideo", src); + savedFiles[src] = file; + pasteNodeAtCaret(img, textArea); + }, + "image/jpeg", + 0.95, + ); + }, 100); // https://stackoverflow.com/a/71900837/3872976 }, false); video.src = src; + video.load(); } else { errCb(`Browser doesn't support playing ${file.type}`); } @@ -573,7 +583,7 @@ export function pasteImgToTextArea(file: File, textArea: HTMLElement, errCb: Fun const img = blobToImg(file); pasteNodeAtCaret(img, textArea); } else if (file.type.includes("video")) { - pasteBlobVideoToTextArea(file, textArea, "v", errCb); + pasteBlobVideoToTextArea(file, textArea, ImageType.VIDEO, errCb); } else { errCb(`Pasted file type ${file.type}, which is not an image`); } @@ -606,7 +616,7 @@ export function getMessageData(userMessage: HTMLElement, messageModel?: MessageM const serverId = parseInt(img.getAttribute("serverId")!); const asGiphy = img.className.includes(PASTED_GIPHY_CLASS) ? img.getAttribute("url") : null; const asGiphyPreview = img.className.includes(PASTED_GIPHY_CLASS) ? img.getAttribute("webp") : null; - const videoType: BlobType = img.getAttribute("videoType")! as BlobType; + const videoType: ImageType = img.getAttribute("videoType")! as ImageType; let elSymbol = oldSymbol; if (!elSymbol) { @@ -624,17 +634,17 @@ export function getMessageData(userMessage: HTMLElement, messageModel?: MessageM return; } } - let type: BlobType; + let type: ImageType; if (videoType) { type = videoType; } else if (assAudio) { - type = "a"; + type = ImageType.AUDIO_RECORD; } else if (assFile) { - type = "f"; + type = ImageType.FILE; } else if (asGiphy) { - type = "g"; + type = ImageType.GIPHY; } else { - type = "i"; + type = ImageType.IMAGE; } let url: string; diff --git a/frontend/src/ts/utils/pubsub.ts b/frontend/src/ts/utils/pubsub.ts new file mode 100644 index 000000000..95a195d2f --- /dev/null +++ b/frontend/src/ts/utils/pubsub.ts @@ -0,0 +1,34 @@ +import { + DefaultWsInMessage, + HandlerName +} from "@common/ws/common"; +import {DefaultP2pMessage} from "@/ts/types/messages/p2p"; + + +export function Subscribe, A extends string = string, H extends HandlerName = HandlerName, D extends any = any>() { + return (target: any, memberName: K['action'], propertyDescriptor: + TypedPropertyDescriptor<((firstParameter: K["data"]) => void)> + | TypedPropertyDescriptor<(() => void)> + | TypedPropertyDescriptor<(() => Promise)> + | TypedPropertyDescriptor<((firstParameter: K["data"]) => Promise)> + ) => { + if (!target.__handlers) { + target.__handlers = {} + } + target.__handlers[memberName] = memberName; + }; +} + +export function P2PSubscribe, A extends string = string, D extends any = any>() { + return (target: any, memberName: K['action'], propertyDescriptor: + TypedPropertyDescriptor<((firstParameter: K["data"]) => void)> + | TypedPropertyDescriptor<(() => void)> + | TypedPropertyDescriptor<(() => Promise)> + | TypedPropertyDescriptor<((firstParameter: K) => Promise)> + ) => { + if (!target.__p2p_handlers) { + target.__p2p_handlers = {} + } + target.__p2p_handlers[memberName] = memberName; + }; +} diff --git a/frontend/src/ts/utils/pureFunctions.ts b/frontend/src/ts/utils/pureFunctions.ts index cd996878e..53549b679 100644 --- a/frontend/src/ts/utils/pureFunctions.ts +++ b/frontend/src/ts/utils/pureFunctions.ts @@ -1,11 +1,11 @@ -import type { - FileModel, - MessageModel, -} from "@/ts/types/model"; +import {MessageStatus} from "@common/model/enum/message.status"; +import type {FileModel, MessageModel} from "@/ts/types/model"; +import {MessageStatusInner} from "@/ts/types/model"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; import type {MessageSender} from "@/ts/types/types"; import {ALLOW_EDIT_MESSAGE_IF_UPDATE_HAPPENED_MS_AGO} from "@/ts/utils/consts"; + export function bytesToSize(bytes: number): string { const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; if (bytes < 1) { @@ -115,7 +115,7 @@ export function editMessageWs( transfer: Boolean(messageContent) || messageId > 0 ? { // TODO can this be simplified? error: null, upload: null, - xhr: null, + abortFn: null, } : null, time, tags, @@ -123,12 +123,12 @@ export function editMessageWs( isEditingActive: oldMessage ? oldMessage.isEditingActive : false, isThreadOpened: oldMessage ? oldMessage.isThreadOpened : false, parentMessage, - status: shouldBeSynced ? "sending" : "on_server", + status: shouldBeSynced ? MessageStatusInner.SENDING : MessageStatus.ON_SERVER, content: messageContent, symbol, edited, files, - userId: store.userInfo?.userId!, + userId: store.userInfo?.id!, }; store.addMessage(mm); if (shouldBeSynced) { // Message hasn't been sync to server and was deleted localy diff --git a/frontend/src/ts/utils/runtimeConsts.ts b/frontend/src/ts/utils/runtimeConsts.ts index c9e1852a6..d969033b2 100644 --- a/frontend/src/ts/utils/runtimeConsts.ts +++ b/frontend/src/ts/utils/runtimeConsts.ts @@ -1,9 +1,5 @@ import mobile from "is-mobile"; -import { - BACKEND_ADDRESS, - PUBLIC_PATH, - WEBRTC_CONFIG, -} from "@/ts/utils/consts"; +import {BACKEND_ADDRESS, IS_SSL, PUBLIC_PATH, WEBRTC_CONFIG} from "@/ts/utils/consts"; export const isMobile: boolean = mobile.isMobile(); diff --git a/frontend/src/ts/webrtc/AbstractPeerConnection.ts b/frontend/src/ts/webrtc/AbstractPeerConnection.ts index cc5bd6c09..9a5b56d17 100644 --- a/frontend/src/ts/webrtc/AbstractPeerConnection.ts +++ b/frontend/src/ts/webrtc/AbstractPeerConnection.ts @@ -1,28 +1,28 @@ +import type {CheckTransferDestroyMessage} from "@/ts/types/messages/inner/check.transfer.destroy"; +import type {ConnectToRemoteMessage, ConnectToRemoteMessageBody} from "@/ts/types/messages/inner/connect.to.remote"; +import { + SendRtcDataWsInBody, +} from "@common/ws/message/peer-connection/send.rtc.data"; +import type {HandlerName} from "@common/ws/common"; import type {Logger} from "lines-logger"; import loggerFactory from "@/ts/instances/loggerFactory"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import {bytesToSize} from "@/ts/utils/pureFunctions"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; import Subscription from "@/ts/classes/Subscription"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type {HandlerName} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - CheckTransferDestroy, - ConnectToRemoteMessage, -} from "@/ts/types/messages/innerMessages"; -import type {SendRtcDataMessage} from "@/ts/types/messages/wsInMessages"; + import {WEBRTC_RUNTIME_CONFIG} from "@/ts/utils/runtimeConsts"; +import {Subscribe} from "@/ts/utils/pubsub"; +import type {SendRtcDataWsInMessage} from "@common/ws/message/peer-connection/send.rtc.data"; +import {SessionHolder} from "@/ts/types/types"; +import AbstractMessageProcessor from "@/ts/message_handlers/AbstractMessageProcessor"; -export default abstract class AbstractPeerConnection extends MessageHandler { +export default abstract class AbstractPeerConnection extends AbstractMessageProcessor { protected offerCreator: boolean = false; - protected sendRtcDataQueue: SendRtcDataMessage[] = []; - - protected readonly opponentWsId: string; - - protected readonly connectionId: string; + protected sendRtcDataQueue: SendRtcDataWsInBody[] = []; protected logger: Logger; @@ -30,16 +30,8 @@ export default abstract class AbstractPeerConnection extends MessageHandler { protected sdpConstraints: any; - protected readonly wsHandler: WsHandler; - - protected readonly store: DefaultStore; - - protected readonly roomId: number; - protected sendChannel: RTCDataChannel | null = null; - protected readonly sub: Subscription; - private readonly pc_constraints: unknown = { optional: [/* Firefox*/ /* {DtlsSrtpKeyAgreement: true},*/ @@ -47,20 +39,67 @@ export default abstract class AbstractPeerConnection extends MessageHandler { ], }; - public constructor(roomId: number, connectionId: string, opponentWsId: string, ws: WsHandler, store: DefaultStore, sub: Subscription) { - super(); - this.roomId = roomId; - this.connectionId = connectionId; - this.opponentWsId = opponentWsId; - this.sub = sub; + public constructor( + private readonly roomId: number, + private readonly connectionId: string, + private readonly opponentWsId: string, + private readonly wsHandler: WsApi, + private readonly store: DefaultStore, + private readonly sub: Subscription, + private readonly sessionHolder: SessionHolder, + ) { + super("p2p"); this.sub.subscribe(Subscription.allPeerConnectionsForTransfer(connectionId), this); this.sub.subscribe(this.mySubscriberId, this); - this.wsHandler = ws; - this.store = store; this.logger = loggerFactory.getLoggerColor(`peer:${this.connectionId}:${this.opponentWsId}`, "#960055"); this.logger.log("Created {}", this.constructor.name)(); } + + public sendRawTextToServer(message: string): boolean { + if (this.isChannelOpened) { + this.sendChannel!.send(message); + return true; + } + return false; + } + + private get isChannelOpened(): boolean { + return this.sendChannel?.readyState === "open"; + } + + + protected setupEvents() { + this.sendChannel!.onmessage = this.onChannelMessage.bind(this); + this.sendChannel!.onopen = () => { + this.logger.log("Channel opened")(); + if (this.sessionHolder.wsConnectionId! > this.opponentWsId) { + this.syncMessages(); + } + this.store.addLiveConnectionToRoom({ + connection: this.opponentWsId, + roomId: this.roomId, + }); + }; + this.sendChannel!.onclose = () => { + // This.syncMessageLock = false; // just for the case, not nessesary + this.onDropConnection("Data channel closed"); + }; + } + + onDropConnection(reason: string) { + this.logger.log(`Closed channel ${reason}`)(); + super.onDropConnection(reasong); + if (this.store.userInfo) { + // Otherwise we logged out + this.store.removeLiveConnectionToRoom({ + connection: this.opponentWsId, + roomId: this.roomId, + }); + } + } + + abstract get connectedToRemote(): boolean; abstract set connectedToRemote(v: boolean); @@ -85,16 +124,18 @@ export default abstract class AbstractPeerConnection extends MessageHandler { } this.sub.unsubscribe(Subscription.allPeerConnectionsForTransfer(this.connectionId), this); this.sub.unsubscribe(Subscription.getPeerConnectionId(this.connectionId, this.opponentWsId), this); - const message: CheckTransferDestroy = { // Destroy parent TransferHandler + const message: CheckTransferDestroyMessage = { // Destroy parent TransferHandler handler: Subscription.getTransferId(this.connectionId), action: "checkTransferDestroy", allowZeroSubscribers: true, - wsOpponentId: this.opponentWsId, + data: { + wsOpponentId: this.opponentWsId, + }, }; this.sub.notify(message); } - public createPeerConnection(arg?: ConnectToRemoteMessage) { + public createPeerConnection(arg?: ConnectToRemoteMessageBody) { this.logger.log("Creating RTCPeerConnection with config {} {}", WEBRTC_RUNTIME_CONFIG, this.pc_constraints)(); if (!window.RTCPeerConnection) { throw Error("Your browser doesn't support RTCPeerConnection"); @@ -185,12 +226,13 @@ export default abstract class AbstractPeerConnection extends MessageHandler { // Destroy(Des) // This event comes from websocket from server, which is created by another PC - public async sendRtcData(message: SendRtcDataMessage) { + @Subscribe() + public async sendRtcData(message: SendRtcDataWsInBody) { if (!this.connectedToRemote) { this.logger.warn("Putting sendrtc data event to the queue")(); this.sendRtcDataQueue.push(message); } else { - const data: RTCIceCandidateInit | RTCSessionDescriptionInit | {message: unknown} = message.content; + const {content: data} = message; if (this.pc!.iceConnectionState && this.pc!.iceConnectionState !== "closed") { if ((data).sdp) { this.logger.log("Creating answer")(); diff --git a/frontend/src/ts/webrtc/BaseTransferHandler.ts b/frontend/src/ts/webrtc/BaseTransferHandler.ts index 77d16fe8a..e1d6db985 100644 --- a/frontend/src/ts/webrtc/BaseTransferHandler.ts +++ b/frontend/src/ts/webrtc/BaseTransferHandler.ts @@ -1,16 +1,15 @@ import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type NotifierHandler from "@/ts/classes/NotificationHandler"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; import Subscription from "@/ts/classes/Subscription"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -export default abstract class BaseTransferHandler extends MessageHandler { +export default abstract class BaseTransferHandler { protected connectionId: string | null = null; - protected readonly wsHandler: WsHandler; + protected readonly wsHandler: WsApi; protected readonly notifier: NotifierHandler; @@ -22,8 +21,7 @@ export default abstract class BaseTransferHandler extends MessageHandler { protected readonly sub: Subscription; - public constructor(roomId: number, wsHandler: WsHandler, notifier: NotifierHandler, store: DefaultStore, sub: Subscription) { - super(); + public constructor(roomId: number, wsHandler: WsApi, notifier: NotifierHandler, store: DefaultStore, sub: Subscription) { this.roomId = roomId; this.notifier = notifier; this.wsHandler = wsHandler; diff --git a/frontend/src/ts/webrtc/FileAndCallTransfer.ts b/frontend/src/ts/webrtc/FileAndCallTransfer.ts index 7c99e1b77..5897cc9de 100644 --- a/frontend/src/ts/webrtc/FileAndCallTransfer.ts +++ b/frontend/src/ts/webrtc/FileAndCallTransfer.ts @@ -1,9 +1,16 @@ -import type {CheckTransferDestroy} from "@/ts/types/messages/innerMessages"; import BaseTransferHandler from "@/ts/webrtc/BaseTransferHandler"; import Subscription from "@/ts/classes/Subscription"; +import type { + CheckTransferDestroyMessage, +} from "@/ts/types/messages/inner/check.transfer.destroy"; +import { + CheckTransferDestroyBody, +} from "@/ts/types/messages/inner/check.transfer.destroy"; +import {Subscribe} from "@/ts/utils/pubsub"; export abstract class FileAndCallTransfer extends BaseTransferHandler { - public checkTransferDestroy(payload: CheckTransferDestroy) { + @Subscribe() + public checkTransferDestroy(payload: CheckTransferDestroyBody) { this.logger.log("Checkign destroy")(); if (this.connectionId && this.sub.getNumberOfSubscribers(Subscription.allPeerConnectionsForTransfer(this.connectionId)) === 0) { this.onDestroy(); diff --git a/frontend/src/ts/webrtc/WebRtcApi.ts b/frontend/src/ts/webrtc/WebRtcApi.ts index 05a1e47ba..8b5a43f65 100644 --- a/frontend/src/ts/webrtc/WebRtcApi.ts +++ b/frontend/src/ts/webrtc/WebRtcApi.ts @@ -1,16 +1,28 @@ +import {NotifyCallActiveWsInBody} from "@common/ws/message/webrtc/notify.call.active"; +import type { + NotifyCallActiveWsInMessage, +} from "@common/ws/message/webrtc/notify.call.active"; +import type {OfferMessageWsInMessage} from "@common/ws/message/webrtc/offer.message"; +import type {HandlerName} from "@common/ws/common"; +import type {ChangeP2pRoomInfoMessage} from "@/ts/types/messages/inner/change.p2p.room.info"; +import { + ChangeOnlineBody, +} from "@/ts/types/messages/inner/change.user.online.info"; +import type { + ChangeOnlineMessage, +} from "@/ts/types/messages/inner/change.user.online.info"; +import type {InternetAppearMessage} from "@/ts/types/messages/inner/internet.appear"; +import type {LogoutMessage} from "@/ts/types/messages/inner/logout"; + import loggerFactory from "@/ts/instances/loggerFactory"; import type {Logger} from "lines-logger"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type {ReceivingFile} from "@/ts/types/model"; import {FileTransferStatus} from "@/ts/types/model"; import FileHandler from "@/ts/webrtc/file/FileHandler"; import type NotifierHandler from "@/ts/classes/NotificationHandler"; -import MessageHandler from "@/ts/message_handlers/MesageHandler"; import {MAX_ACCEPT_FILE_SIZE_WO_FS_API} from "@/ts/utils/consts"; -import { - requestFileSystem, - resolveMediaUrl, -} from "@/ts/utils/htmlApi"; +import {requestFileSystem, resolveMediaUrl} from "@/ts/utils/htmlApi"; import FileReceiverPeerConnection from "@/ts/webrtc/file/FileReceiveerPeerConnection"; import Subscription from "@/ts/classes/Subscription"; import CallHandler from "@/ts/webrtc/call/CallHandler"; @@ -19,43 +31,39 @@ import type {DefaultStore} from "@/ts/classes/DefaultStore"; import {browserVersion} from "@/ts/utils/runtimeConsts"; import MessageTransferHandler from "@/ts/webrtc/message/MessageTransferHandler"; import {bytesToSize} from "@/ts/utils/pureFunctions"; -import type { - NotifyCallActiveMessage, - OfferCall, - OfferFile, - OfferMessage, - WebRtcSetConnectionIdMessage, -} from "@/ts/types/messages/wsInMessages"; -import type { - HandlerName, - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; import type {VideoType} from "@/ts/types/types"; -import type { - ChangeP2pRoomInfoMessage, - ChangeUserOnlineInfoMessage, - InternetAppearMessage, - LogoutMessage, -} from "@/ts/types/messages/innerMessages"; + import type {MessageHelper} from "@/ts/message_handlers/MessageHelper"; import type {MessageSenderProxy} from "@/ts/message_handlers/MessageSenderProxy"; - -export default class WebRtcApi extends MessageHandler { +import type { + OfferFileResponse, +} from "@common/ws/message/webrtc/offer.file"; +import { + OfferFileBody, +} from "@common/ws/message/webrtc/offer.file"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {ChangeP2pRoomInfoMessageBody} from "@/ts/types/messages/inner/change.p2p.room.info"; +import type { + OfferCallWsInMessage, +} from "@common/ws/message/webrtc/offer.call"; +import { + OfferCallWsInBody, +} from "@common/ws/message/webrtc/offer.call"; +import {OfferMessageWsInBody} from "@common/ws/message/webrtc/offer.message"; +import type {AnswerCallMessage} from "@/ts/types/messages/inner/answer.call"; +import type {DeclineCallMessage} from "@/ts/types/messages/inner/decline.call"; +import type {VideoAnswerCallMessage} from "@/ts/types/messages/inner/video.answer.call"; +import type {DeclineSendingMessage} from "@/ts/types/messages/peer-connection/decline.sending"; +import type {DeclineFileReply} from "@/ts/types/messages/peer-connection/decline.file.reply"; +import type {RetryFileReply} from "@/ts/types/messages/peer-connection/retry.file.reply"; +import {WebRtcSetConnectionIdBody} from "@common/model/webrtc.base"; +import {AcceptFileReplyInnerSystemMessage} from "@/ts/types/messages/peer-connection/accept.file.reply"; + + +export default class WebRtcApi { protected logger: Logger; - protected readonly handlers: HandlerTypes = { - offerFile: > this.offerFile, - changeDevices: > this.changeDevices, - offerCall: > this.offerCall, - offerMessage: > this.offerMessage, - changeOnline: > this.changeOnline, - logout: > this.logout, - internetAppear: > this.internetAppear, - notifyCallActive: > this.notifyCallActive, - }; - - private readonly wsHandler: WsHandler; + private readonly wsHandler: WsApi; private readonly store: DefaultStore; @@ -71,8 +79,7 @@ export default class WebRtcApi extends MessageHandler { private readonly sub: Subscription; - public constructor(ws: WsHandler, store: DefaultStore, notifier: NotifierHandler, messageHelper: MessageHelper, sub: Subscription) { - super(); + public constructor(ws: WsApi, store: DefaultStore, notifier: NotifierHandler, messageHelper: MessageHelper, sub: Subscription) { this.sub = sub; this.sub.subscribe("webrtc", this); this.wsHandler = ws; @@ -87,7 +94,8 @@ export default class WebRtcApi extends MessageHandler { this.messageSenderProxy = messageSenderProxy; } - public offerCall(message: OfferCall) { + @Subscribe() + public offerCall(message: OfferCallWsInBody) { this.getCallHandler(message.roomId).initAndDisplayOffer(message); } @@ -95,7 +103,8 @@ export default class WebRtcApi extends MessageHandler { this.getCallHandler(roomId).joinCall(); } - public async changeDevices(m: ChangeP2pRoomInfoMessage): Promise { + @Subscribe() + public async changeDevices(m: ChangeP2pRoomInfoMessageBody): Promise { this.logger.log("change devices {}", m)(); this.updateCallTransfer(m); await this.updateMessagesTransfer(m); @@ -109,15 +118,18 @@ export default class WebRtcApi extends MessageHandler { }); } - public internetAppear(m: InternetAppearMessage) { + @Subscribe() + public internetAppear() { this.initAndSyncMessages(); } - public notifyCallActive(m: NotifyCallActiveMessage) { + @Subscribe() + public notifyCallActive(m: NotifyCallActiveWsInBody) { this.getCallHandler(m.roomId).addOpponent(m.connId, m.userId, m.opponentWsId); } - public changeOnline(message: ChangeUserOnlineInfoMessage) { + @Subscribe() + public changeOnline(message: ChangeOnlineBody) { this.store.roomsArray.filter((r) => r.callInfo.callActive && r.users.includes(message.userId)).forEach((r) => { if (message.changeType === "appear_online") { this.getCallHandler(r.id).createCallPeerConnection({ @@ -138,34 +150,38 @@ export default class WebRtcApi extends MessageHandler { }); } - public offerMessage(message: OfferMessage) { + @Subscribe() + public offerMessage(message: OfferMessageWsInBody) { this.getMessageHandler(message.roomId).acceptConnection(message); } public acceptFile(connId: string, webRtcOpponentId: string) { - this.sub.notify({ + this.sub.notify({ action: "acceptFileReply", handler: Subscription.getPeerConnectionId(connId, webRtcOpponentId), + data: null, }); } public declineSending(connId: string, webRtcOpponentId: string) { - this.sub.notify({ + this.sub.notify({ action: "declineSending", handler: Subscription.getPeerConnectionId(connId, webRtcOpponentId), + data: null, }); } public declineFile(connId: string, webRtcOpponentId: string) { - this.sub.notify({ + this.sub.notify({ action: "declineFileReply", handler: Subscription.getPeerConnectionId(connId, webRtcOpponentId), + data: null, }); } public async sendFileOffer(file: File, channel: number, threadId: number | null) { if (file.size > 0) { - const e: WebRtcSetConnectionIdMessage = await this.wsHandler.offerFile(channel, browserVersion, file.name, file.size, threadId); + const e: WebRtcSetConnectionIdBody = await this.wsHandler.offerFile(channel, browserVersion, file.name, file.size, threadId); new FileHandler(channel, threadId, e.connId, this.wsHandler, this.notifier, this.store, file, this.wsHandler.convertServerTimeToPC(e.time), this.sub); } else { this.store.growlError(`File ${file.name} size is 0. Skipping sending it...`); @@ -173,9 +189,10 @@ export default class WebRtcApi extends MessageHandler { } public retryFile(connId: string, webRtcOpponentId: string) { - this.sub.notify({ + this.sub.notify({ action: "retryFileReply", handler: Subscription.getPeerConnectionId(connId, webRtcOpponentId), + data: null, }); } @@ -200,24 +217,30 @@ export default class WebRtcApi extends MessageHandler { } public answerCall(connId: string) { - this.sub.notify({ + const message: AnswerCallMessage = { action: "answerCall", handler: Subscription.getTransferId(connId), - }); + data: null, + }; + this.sub.notify(message); } public declineCall(connId: string) { - this.sub.notify({ + const message: DeclineCallMessage = { action: "declineCall", handler: Subscription.getTransferId(connId), - }); + data: null, + }; + this.sub.notify(message); } public videoAnswerCall(connId: string) { - this.sub.notify({ + const m: VideoAnswerCallMessage = { action: "videoAnswerCall", handler: Subscription.getTransferId(connId), - }); + data: null, + }; + this.sub.notify(m); } public hangCall(roomId: number) { @@ -236,7 +259,8 @@ export default class WebRtcApi extends MessageHandler { this.callHandlers[roomId].toggleDevice(videoType); } - public logout(m: LogoutMessage) { + @Subscribe() + public logout() { for (const k in this.callHandlers) { this.callHandlers[k].hangCall(); } @@ -245,8 +269,9 @@ export default class WebRtcApi extends MessageHandler { } } - public offerFile(message: OfferFile): void { - const limitExceeded = message.content.size > MAX_ACCEPT_FILE_SIZE_WO_FS_API && !requestFileSystem; + @Subscribe() + public offerFile(message: OfferFileBody): void { + const limitExceeded = message.size > MAX_ACCEPT_FILE_SIZE_WO_FS_API && !requestFileSystem; const payload: ReceivingFile = { roomId: message.roomId, opponentWsId: message.opponentWsId, @@ -256,17 +281,17 @@ export default class WebRtcApi extends MessageHandler { userId: message.userId, error: limitExceeded ? `Your browser doesn't support receiving files over ${bytesToSize(MAX_ACCEPT_FILE_SIZE_WO_FS_API)}` : null, connId: message.connId, - fileName: message.content.name, + fileName: message.name, time: this.wsHandler.convertServerTimeToPC(message.time), upload: { uploaded: 0, - total: message.content.size, + total: message.size, }, }; - this.notifier.showNotification(this.store.allUsersDict[message.userId].user, { - body: `Sends file ${message.content.name}`, + this.notifier.showNotification(this.store.allUsersDict[message.userId].username, { + body: `Sends file ${message.name}`, requireInteraction: true, - icon: resolveMediaUrl(this.store.allUsersDict[message.userId].image) || faviconUrl, + icon: resolveMediaUrl(this.store.allUsersDict[message.userId].thumbnail) || faviconUrl, replaced: 1, }); this.store.addReceivingFile(payload); @@ -275,11 +300,11 @@ export default class WebRtcApi extends MessageHandler { } this.wsHandler.replyFile(message.connId, browserVersion); if (!limitExceeded) { - new FileReceiverPeerConnection(message.roomId, message.connId, message.opponentWsId, this.wsHandler, this.store, message.content.size, this.sub); + new FileReceiverPeerConnection(message.roomId, message.connId, message.opponentWsId, this.wsHandler, this.store, message.size, this.sub); } } - private updateCallTransfer(m: ChangeP2pRoomInfoMessage) { + private updateCallTransfer(m: ChangeP2pRoomInfoMessageBody) { if (m.changeType === "someone_joined" || m.changeType === "someone_left") { this.store.roomsArray.filter((r) => r.callInfo.callActive && r.users.includes(m.userId!)).forEach((r) => { if (m.changeType === "someone_joined") { @@ -306,7 +331,7 @@ export default class WebRtcApi extends MessageHandler { } } - private async updateMessagesTransfer(m: ChangeP2pRoomInfoMessage) { + private async updateMessagesTransfer(m: ChangeP2pRoomInfoMessageBody) { if (m.changeType === "i_deleted") { // Destroy my room const mh: MessageTransferHandler = this.messageHandlers[m.roomId]; if (mh) { diff --git a/frontend/src/ts/webrtc/call/CallHandler.ts b/frontend/src/ts/webrtc/call/CallHandler.ts index b632fd953..02d10120c 100644 --- a/frontend/src/ts/webrtc/call/CallHandler.ts +++ b/frontend/src/ts/webrtc/call/CallHandler.ts @@ -1,13 +1,23 @@ +import type {CallStatus} from "@common/model/webrtc.base"; +import type {AcceptCallWsInMessage} from "@common/ws/message/webrtc-transfer/accept.call"; +import {ReplyCallWsInBody} from "@common/ws/message/webrtc-transfer/reply.call"; +import type { + ReplyCallWsInMessage, +} from "@common/ws/message/webrtc-transfer/reply.call"; +import type {ChangeStreamMessage} from "@/ts/types/messages/inner/change.stream"; import { - browserVersion, - isChrome, - isMobile, -} from "@/ts/utils/runtimeConsts"; -import Subscription from "@/ts/classes/Subscription"; + CheckTransferDestroyBody, +} from "@/ts/types/messages/inner/check.transfer.destroy"; import type { - CallsInfoModel, - IncomingCallModel, -} from "@/ts/types/model"; + CheckTransferDestroyMessage, +} from "@/ts/types/messages/inner/check.transfer.destroy"; +import type {ConnectToRemoteMessage} from "@/ts/types/messages/inner/connect.to.remote"; +import type {DestroyPeerConnectionMessage} from "@/ts/types/messages/inner/destroy.peer.connection"; +import type {RouterNavigateMessage} from "@/ts/types/messages/inner/router.navigate"; + +import {browserVersion, isChrome, isMobile} from "@/ts/utils/runtimeConsts"; +import Subscription from "@/ts/classes/Subscription"; +import type {CallsInfoModel, IncomingCallModel} from "@/ts/types/model"; import type { BooleanIdentifier, JsAudioAnalyzer, @@ -16,53 +26,26 @@ import type { SetDevices, } from "@/ts/types/types"; import {VideoType} from "@/ts/types/types"; -import { - CHROME_EXTENSION_ID, - CHROME_EXTENSION_URL, -} from "@/ts/utils/consts"; -import { - extractError, - getChromeVersion, -} from "@/ts/utils/pureFunctions"; -import { - createMicrophoneLevelVoice, - getAverageAudioLevel, - removeAudioProcesssor, -} from "@/ts/utils/audioprocc"; +import {CHROME_EXTENSION_ID, CHROME_EXTENSION_URL} from "@/ts/utils/consts"; +import {extractError, getChromeVersion} from "@/ts/utils/pureFunctions"; +import {createMicrophoneLevelVoice, getAverageAudioLevel, removeAudioProcesssor} from "@/ts/utils/audioprocc"; import CallSenderPeerConnection from "@/ts/webrtc/call/CallSenderPeerConnection"; import CallReceiverPeerConnection from "@/ts/webrtc/call/CallReceiverPeerConnection"; -import type { - CallStatus, - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - AcceptCallMessage, - OfferCall, - ReplyCallMessage, -} from "@/ts/types/messages/wsInMessages"; -import type { - ChangeStreamMessage, - CheckTransferDestroy, - ConnectToRemoteMessage, - DestroyPeerConnectionMessage, - RouterNavigateMessage, -} from "@/ts/types/messages/innerMessages"; import {FileAndCallTransfer} from "@/ts/webrtc/FileAndCallTransfer"; import {stopVideo} from "@/ts/utils/htmlApi"; +import {Subscribe} from "@/ts/utils/pubsub"; +import type {AnswerCallMessage} from "@/ts/types/messages/inner/answer.call"; +import type {VideoAnswerCallMessage} from "@/ts/types/messages/inner/video.answer.call"; +import type {DeclineCallMessage} from "@/ts/types/messages/inner/decline.call"; +import type {OfferCallWsInBody} from "@common/ws/message/webrtc/offer.call"; +import { + OfferCallWsInMessage, +} from "@common/ws/message/webrtc/offer.call"; +import {AcceptCallWsInBody} from "@common/ws/message/webrtc-transfer/accept.call"; export default class CallHandler extends FileAndCallTransfer { - protected readonly handlers: HandlerTypes = { - answerCall: this.answerCall, - videoAnswerCall: this.videoAnswerCall, - declineCall: this.declineCall, - replyCall: > this.replyCall, - acceptCall: > this.acceptCall, - checkTransferDestroy: > this.checkTransferDestroy, - }; - private canvas: HTMLCanvasElement | null = null; private localStream: MediaStream | null = null; @@ -77,7 +60,7 @@ export default class CallHandler extends FileAndCallTransfer { return this.store.roomsDict[this.roomId].callInfo; } - public checkTransferDestroy(payload: CheckTransferDestroy) { + public checkTransferDestroy(payload: CheckTransferDestroyBody) { this.removeOpponent(payload.wsOpponentId); super.checkTransferDestroy(payload); } @@ -115,7 +98,8 @@ export default class CallHandler extends FileAndCallTransfer { return this.connectionId; } - public acceptCall(message: AcceptCallMessage) { + @Subscribe() + public acceptCall(message: AcceptCallWsInBody) { if (this.callStatus !== "received_offer") { // If we're call initiator if (!this.connectionId) { throw Error("Conn is is null"); @@ -123,7 +107,9 @@ export default class CallHandler extends FileAndCallTransfer { const payload: ConnectToRemoteMessage = { action: "connectToRemote", handler: Subscription.getPeerConnectionId(this.connectionId, message.opponentWsId), - stream: this.localStream, + data: { + stream: this.localStream, + }, }; this.sub.notify(payload); } else { @@ -235,7 +221,9 @@ export default class CallHandler extends FileAndCallTransfer { handler: Subscription.allPeerConnectionsForTransfer(this.connectionId!), action: "streamChanged", allowZeroSubscribers: true, - newStream: stream!, + data: { + newStream: stream!, + }, }; this.sub.notify(message); } catch (e: any) { @@ -373,11 +361,12 @@ export default class CallHandler extends FileAndCallTransfer { } } - public replyCall(message: ReplyCallMessage) { + @Subscribe() + public replyCall(message: ReplyCallWsInBody) { this.createCallPeerConnection(message); } - public initAndDisplayOffer(message: OfferCall) { + public initAndDisplayOffer(message: OfferCallWsInBody) { this.setCallStatus("received_offer"); if (this.connectionId) { this.logger.error("Old connId still exists {}", this.connectionId)(); @@ -395,6 +384,7 @@ export default class CallHandler extends FileAndCallTransfer { this.createCallPeerConnection(message); } + @Subscribe() public answerCall() { this.doAnswer(false); } @@ -419,12 +409,13 @@ export default class CallHandler extends FileAndCallTransfer { this.attachLocalStream(stream); this.wsHandler.acceptCall(this.connectionId!); this.connectToRemote(); - const message1: RouterNavigateMessage = { + this.sub.notify({ handler: "router", action: "navigate", - to: `/chat/${this.roomId}`, - }; - this.sub.notify(message1); + data: { + to: `/chat/${this.roomId}`, + }, + }); } public async joinCall() { @@ -443,6 +434,7 @@ export default class CallHandler extends FileAndCallTransfer { this.connectToRemote(); } + @Subscribe() public videoAnswerCall() { this.doAnswer(true); } @@ -478,6 +470,7 @@ export default class CallHandler extends FileAndCallTransfer { this.acceptedPeers.length = 0; // = [] } + @Subscribe() public declineCall() { this.store.setIncomingCall(null); this.wsHandler.destroyCallConnection(this.connectionId!, "decline"); @@ -497,12 +490,12 @@ export default class CallHandler extends FileAndCallTransfer { this.logger.error("Can't close connections since it's null")(); return; } - const message: DestroyPeerConnectionMessage = { + this.sub.notify({ action: "destroy", handler: Subscription.allPeerConnectionsForTransfer(this.connectionId), allowZeroSubscribers: true, - }; - this.sub.notify(message); + data: null, + }); } else { this.onDestroy(); } @@ -524,12 +517,13 @@ export default class CallHandler extends FileAndCallTransfer { private connectToRemote() { this.acceptedPeers.forEach((e) => { - const message: ConnectToRemoteMessage = { + this.sub.notify({ action: "connectToRemote", - stream: this.localStream, + data: { + stream: this.localStream, + }, handler: Subscription.getPeerConnectionId(this.connectionId!, e), - }; - this.sub.notify(message); + }); }); } diff --git a/frontend/src/ts/webrtc/call/CallPeerConnection.ts b/frontend/src/ts/webrtc/call/CallPeerConnection.ts index cb25c46b7..2eea7d5d5 100644 --- a/frontend/src/ts/webrtc/call/CallPeerConnection.ts +++ b/frontend/src/ts/webrtc/call/CallPeerConnection.ts @@ -1,47 +1,30 @@ -import { - createMicrophoneLevelVoice, - getAverageAudioLevel, - removeAudioProcesssor, -} from "@/ts/utils/audioprocc"; +import type {ChangeStreamMessage} from "@/ts/types/messages/inner/change.stream"; +import type {ConnectToRemoteMessage} from "@/ts/types/messages/inner/connect.to.remote"; +import type {DestroyPeerConnectionMessage} from "@/ts/types/messages/inner/destroy.peer.connection"; + +import {createMicrophoneLevelVoice, getAverageAudioLevel, removeAudioProcesssor} from "@/ts/utils/audioprocc"; import AbstractPeerConnection from "@/ts/webrtc/AbstractPeerConnection"; import type { JsAudioAnalyzer, SetCallOpponent, SetOpponentAnchor, - SetOpponentVoice, + SetOpponentVoice } from "@/ts/types/types"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type { - ChangeStreamMessage, - ConnectToRemoteMessage, - DestroyPeerConnectionMessage, -} from "@/ts/types/messages/innerMessages"; -import type {DestroyCallConnection} from "@/ts/types/messages/wsInMessages"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import { - getStreamLog, - getTrackLog, -} from "@/ts/utils/pureFunctions"; -import type { - CallInfoModel, - RoomModel, -} from "@/ts/types/model"; + +import {getStreamLog, getTrackLog} from "@/ts/utils/pureFunctions"; +import type {CallInfoModel, RoomModel} from "@/ts/types/model"; import {stopVideo} from "@/ts/utils/htmlApi"; import type Subscription from "@/ts/classes/Subscription"; +import {ConnectToRemoteMessageBody} from "@/ts/types/messages/inner/connect.to.remote"; +import {LoginMessage} from "@/ts/types/messages/inner/login"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {DestroyCallConnectionWsInMessage} from "@common/ws/message/peer-connection/destroy.call.connection"; +import {ChangeStreamMessageBody} from "@/ts/types/messages/inner/change.stream"; -export default abstract class CallPeerConnection extends AbstractPeerConnection { - protected readonly handlers: HandlerTypes = { - destroy: > this.destroy, - streamChanged: > this.streamChanged, - connectToRemote: > this.connectToRemote, - sendRtcData: > this.sendRtcData, - destroyCallConnection: > this.destroyCallConnection, - }; +export default abstract class CallPeerConnection extends AbstractPeerConnection { private audioProcessor: any; // Ontrack can be triggered multiple time, so call this in order to prevent updaing store multiple time @@ -56,7 +39,7 @@ export default abstract class CallPeerConnection extends AbstractPeerConnection connId: string, opponentWsId: string, userId: number, - wsHandler: WsHandler, + wsHandler: WsApi, store: DefaultStore, sub: Subscription, ) { @@ -112,7 +95,7 @@ export default abstract class CallPeerConnection extends AbstractPeerConnection } - public connectToRemote(stream: ConnectToRemoteMessage) { + public connectToRemote(stream: ConnectToRemoteMessageBody) { this.logger.log("Connect to remote")(); this.store.roomsDict[this.roomId].callInfo.calls[this.opponentWsId].connected = true; this.connectedToRemote = true; @@ -138,7 +121,7 @@ export default abstract class CallPeerConnection extends AbstractPeerConnection } } - public createPeerConnection(event: ConnectToRemoteMessage) { + public createPeerConnection(event: ConnectToRemoteMessageBody) { super.createPeerConnection(); if (this.streamTrackApi === "stream") { @@ -166,7 +149,7 @@ export default abstract class CallPeerConnection extends AbstractPeerConnection this.changeStreams(event.stream); } - public streamChanged(payload: ChangeStreamMessage) { + public streamChanged(payload: ChangeStreamMessageBody) { this.logger.log("onStreamChanged {}", payload)(); if (this.pc) { this.changeStreams(payload.newStream); @@ -215,11 +198,13 @@ export default abstract class CallPeerConnection extends AbstractPeerConnection }; } - public destroy(message: DestroyPeerConnectionMessage) { // Called by transfer + @Subscribe() + public destroy() { // Called by transfer this.unsubscribeAndRemoveFromParent(); } - destroyCallConnection(m: DestroyCallConnection) { // Called by opponent devices via ws + @Subscribe() + public destroyCallConnection() { this.unsubscribeAndRemoveFromParent(); } diff --git a/frontend/src/ts/webrtc/call/CallSenderPeerConnection.ts b/frontend/src/ts/webrtc/call/CallSenderPeerConnection.ts index ba30ee8a3..21f6822bc 100644 --- a/frontend/src/ts/webrtc/call/CallSenderPeerConnection.ts +++ b/frontend/src/ts/webrtc/call/CallSenderPeerConnection.ts @@ -1,8 +1,12 @@ +import type { + ConnectToRemoteMessage, + ConnectToRemoteMessageBody +} from "@/ts/types/messages/inner/connect.to.remote"; import CallPeerConnection from "@/ts/webrtc/call/CallPeerConnection"; -import type {ConnectToRemoteMessage} from "@/ts/types/messages/innerMessages"; + export default class CallSenderPeerConnection extends CallPeerConnection { - public async connectToRemote(stream: ConnectToRemoteMessage) { + public async connectToRemote(stream: ConnectToRemoteMessageBody) { super.connectToRemote(stream); await this.createOffer(); } diff --git a/frontend/src/ts/webrtc/file/FileHandler.ts b/frontend/src/ts/webrtc/file/FileHandler.ts index a976c3779..4a8dba113 100644 --- a/frontend/src/ts/webrtc/file/FileHandler.ts +++ b/frontend/src/ts/webrtc/file/FileHandler.ts @@ -1,26 +1,20 @@ +import type {ReplyFileWsInMessage} from "@common/ws/message/webrtc-transfer/reply.file"; import type NotifierHandler from "@/ts/classes/NotificationHandler"; import type {SendingFile} from "@/ts/types/model"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import FileSenderPeerConnection from "@/ts/webrtc/file/FileSenderPeerConnection"; import Subscription from "@/ts/classes/Subscription"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type {ReplyFileMessage} from "@/ts/types/messages/wsInMessages"; import {FileAndCallTransfer} from "@/ts/webrtc/FileAndCallTransfer"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {ReplyFileWsInBody} from "@common/ws/message/webrtc-transfer/reply.file"; export default class FileHandler extends FileAndCallTransfer { - protected readonly handlers: HandlerTypes = { - replyFile: > this.replyFile, - checkTransferDestroy: > this.checkTransferDestroy, - }; private readonly file: File; - public constructor(roomId: number, threadId: number | null, connId: string, wsHandler: WsHandler, notifier: NotifierHandler, store: DefaultStore, file: File, time: number, sub: Subscription) { + public constructor(roomId: number, threadId: number | null, connId: string, wsHandler: WsApi, notifier: NotifierHandler, store: DefaultStore, file: File, time: number, sub: Subscription) { super(roomId, wsHandler, notifier, store, sub); this.file = file; this.setConnectionId(connId); @@ -37,7 +31,9 @@ export default class FileHandler extends FileAndCallTransfer { this.store.addSendingFile(payload); } - public replyFile(message: ReplyFileMessage) { + + @Subscribe() + public replyFile(message: ReplyFileWsInBody) { this.logger.debug("got mes {}", message)(); new FileSenderPeerConnection(this.roomId, message.connId, message.opponentWsId, this.wsHandler, this.store, this.file, message.userId, this.sub); } diff --git a/frontend/src/ts/webrtc/file/FileReceiveerPeerConnection.ts b/frontend/src/ts/webrtc/file/FileReceiveerPeerConnection.ts index a97941aaf..9af3e8200 100644 --- a/frontend/src/ts/webrtc/file/FileReceiveerPeerConnection.ts +++ b/frontend/src/ts/webrtc/file/FileReceiveerPeerConnection.ts @@ -1,37 +1,23 @@ -import type { - SetReceivingFileStatus, - SetReceivingFileUploaded, -} from "@/ts/types/types"; +import type {DestroyFileConnectionWsInMessage} from "@common/ws/message/peer-connection/destroy.file.connection"; +import type {RetryFileWsInMessage} from "@common/ws/message/peer-connection/retry.file"; +import type {SetReceivingFileStatus, SetReceivingFileUploaded} from "@/ts/types/types"; import type {ReceivingFile} from "@/ts/types/model"; import {FileTransferStatus} from "@/ts/types/model"; import {bytesToSize} from "@/ts/utils/pureFunctions"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import {requestFileSystem} from "@/ts/utils/htmlApi"; -import { - MAX_ACCEPT_FILE_SIZE_WO_FS_API, - MAX_BUFFER_SIZE, -} from "@/ts/utils/consts"; +import {MAX_ACCEPT_FILE_SIZE_WO_FS_API, MAX_BUFFER_SIZE} from "@/ts/utils/consts"; import FilePeerConnection from "@/ts/webrtc/file/FilePeerConnection"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - DestroyFileConnectionMessage, - RetryFileMessage, -} from "@/ts/types/messages/wsInMessages"; import type Subscription from "@/ts/classes/Subscription"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {DestroyFileConnectionWsInBody} from "@common/ws/message/peer-connection/destroy.file.connection"; +import {RetryFileReply} from "@/ts/types/messages/peer-connection/retry.file.reply"; +import {AcceptFileReply} from "@/ts/types/messages/peer-connection/accept.file.reply"; +import {DeclineFileReply} from "@/ts/types/messages/peer-connection/decline.file.reply"; + export default class FileReceiverPeerConnection extends FilePeerConnection { - protected readonly handlers: HandlerTypes = { - sendRtcData: > this.sendRtcData, - retryFile: > this.retryFile, - retryFileReply: > this.retryFileReply, - acceptFileReply: > this.acceptFileReply, - declineFileReply: > this.declineFileReply, - destroyFileConnection: > this.destroyFileConnection, - }; private readonly fileSize: number; @@ -49,7 +35,7 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { private retryFileSend: number = 0; - public constructor(roomId: number, connId: string, opponentWsId: string, wsHandler: WsHandler, store: DefaultStore, size: number, sub: Subscription) { + public constructor(roomId: number, connId: string, opponentWsId: string, wsHandler: WsApi, store: DefaultStore, size: number, sub: Subscription) { super(roomId, connId, opponentWsId, wsHandler, store, sub); this.fileSize = size; } @@ -58,6 +44,7 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { return this.store.roomsDict[this.roomId].receivingFiles[this.connectionId]; } + @Subscribe() public retryFileReply() { const now = Date.now(); if (now - this.retryFileSend > 5000) { @@ -66,6 +53,7 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { } } + @Subscribe() public declineFileReply() { this.wsHandler.destroyFileConnection(this.connectionId, "decline"); const rf: SetReceivingFileStatus = { @@ -77,6 +65,8 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { this.unsubscribeAndRemoveFromParent(); } + + @Subscribe() public async acceptFileReply() { try { await this.initFileSystemApi(); @@ -94,7 +84,8 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { this.waitForAnswer(); } - public destroyFileConnection(message: DestroyFileConnectionMessage) { + @Subscribe() + public destroyFileConnection(message: DestroyFileConnectionWsInBody) { const payload: SetReceivingFileStatus = { error: null, status: FileTransferStatus.DECLINED_BY_OPPONENT, @@ -105,7 +96,8 @@ export default class FileReceiverPeerConnection extends FilePeerConnection { this.unsubscribeAndRemoveFromParent(); } - public retryFile(message: RetryFileMessage) { + @Subscribe() + public retryFile() { const payload: SetReceivingFileStatus = { error: null, status: FileTransferStatus.IN_PROGRESS, diff --git a/frontend/src/ts/webrtc/file/FileSenderPeerConnection.ts b/frontend/src/ts/webrtc/file/FileSenderPeerConnection.ts index a3e02b731..1945fa9fa 100644 --- a/frontend/src/ts/webrtc/file/FileSenderPeerConnection.ts +++ b/frontend/src/ts/webrtc/file/FileSenderPeerConnection.ts @@ -1,38 +1,21 @@ -import type { - AddSendingFileTransfer, - SetSendingFileStatus, - SetSendingFileUploaded, -} from "@/ts/types/types"; +import type {DestroyFileConnectionWsInMessage} from "@common/ws/message/peer-connection/destroy.file.connection"; +import type {AcceptFileWsInMessage} from "@common/ws/message/webrtc-transfer/accept.file"; +import type {AddSendingFileTransfer, SetSendingFileStatus, SetSendingFileUploaded} from "@/ts/types/types"; import type {SendingFileTransfer} from "@/ts/types/model"; import {FileTransferStatus} from "@/ts/types/model"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; -import { - bytesToSize, - getDay, -} from "@/ts/utils/pureFunctions"; -import { - READ_CHUNK_SIZE, - SEND_CHUNK_SIZE, -} from "@/ts/utils/consts"; +import type WsApi from "@/ts/message_handlers/WsApi"; +import {bytesToSize, getDay} from "@/ts/utils/pureFunctions"; +import {READ_CHUNK_SIZE, SEND_CHUNK_SIZE} from "@/ts/utils/consts"; import FilePeerConnection from "@/ts/webrtc/file/FilePeerConnection"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; -import type { - AcceptFileMessage, - DestroyFileConnectionMessage, -} from "@/ts/types/messages/wsInMessages"; import type Subscription from "@/ts/classes/Subscription"; +import {Subscribe} from "@/ts/utils/pubsub"; +import {AcceptFileWsInBody} from "@common/ws/message/webrtc-transfer/accept.file"; +import {DestroyFileConnectionWsInBody} from "@common/ws/message/peer-connection/destroy.file.connection"; +import {DeclineSendingMessage} from "@/ts/types/messages/peer-connection/decline.sending"; + export default class FileSenderPeerConnection extends FilePeerConnection { - protected readonly handlers: HandlerTypes = { - destroyFileConnection: > this.destroyFileConnection, - acceptFile: > this.acceptFile, - sendRtcData: > this.sendRtcData, - declineSending: > this.declineSending, - }; private readonly file: File; @@ -46,7 +29,7 @@ export default class FileSenderPeerConnection extends FilePeerConnection { private trackTimeout: number = 0; - public constructor(roomId: number, connId: string, opponentWsId: string, wsHandler: WsHandler, store: DefaultStore, file: File, userId: number, sub: Subscription) { + public constructor(roomId: number, connId: string, opponentWsId: string, wsHandler: WsApi, store: DefaultStore, file: File, userId: number, sub: Subscription) { super(roomId, connId, opponentWsId, wsHandler, store, sub); this.file = file; const asft: AddSendingFileTransfer = { @@ -86,8 +69,9 @@ export default class FileSenderPeerConnection extends FilePeerConnection { } } - public acceptFile(message: AcceptFileMessage) { - this.offset = message.content.received; + @Subscribe() + public acceptFile(message: AcceptFileWsInBody) { + this.offset = message.received; this.createPeerConnection(); const ssfs: SetSendingFileStatus = { status: FileTransferStatus.IN_PROGRESS, @@ -97,7 +81,7 @@ export default class FileSenderPeerConnection extends FilePeerConnection { transfer: this.opponentWsId, }; this.store.setSendingFileStatus(ssfs); - this.setTranseferdAmount(message.content.received); + this.setTranseferdAmount(message.received); try { // Reliable data channels not supported by Chrome this.sendChannel = this.pc!.createDataChannel("sendDataChannel", {reliable: false}); @@ -159,13 +143,14 @@ export default class FileSenderPeerConnection extends FilePeerConnection { } } - public destroyFileConnection(message: DestroyFileConnectionMessage) { + @Subscribe() + public destroyFileConnection(message: DestroyFileConnectionWsInBody) { let isError = false; let status; - if (message.content === "decline") { + if (message.status === "decline") { status = FileTransferStatus.DECLINED_BY_OPPONENT; this.unsubscribeAndRemoveFromParent(); - } else if (message.content === "success") { + } else if (message.status === "success") { status = FileTransferStatus.FINISHED; this.unsubscribeAndRemoveFromParent(); } else { @@ -175,13 +160,14 @@ export default class FileSenderPeerConnection extends FilePeerConnection { const payload: SetSendingFileStatus = { transfer: this.opponentWsId, connId: this.connectionId, - error: isError ? message.content : null, + error: isError ? message.status : null, roomId: this.roomId, status, }; this.store.setSendingFileStatus(payload); } + @Subscribe() public declineSending() { this.unsubscribeAndRemoveFromParent(); const ssfs: SetSendingFileStatus = { diff --git a/frontend/src/ts/webrtc/message/MessagePeerConnection.ts b/frontend/src/ts/webrtc/message/MessagePeerConnection.ts index 97f09e238..58d91c66d 100644 --- a/frontend/src/ts/webrtc/message/MessagePeerConnection.ts +++ b/frontend/src/ts/webrtc/message/MessagePeerConnection.ts @@ -1,63 +1,70 @@ +import {MessageStatus} from "@common/model/enum/message.status"; +import type {SendSetMessagesStatusMessage} from "@/ts/types/messages/inner/send.set.messages.status"; +import { + SyncP2PMessageBody, +} from "@/ts/types/messages/inner/sync.p2p"; +import type { + SyncP2PInnerSystemMessage, +} from "@/ts/types/messages/inner/sync.p2p"; import AbstractPeerConnection from "@/ts/webrtc/AbstractPeerConnection"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; -import type {MessageSupplier} from "@/ts/types/types"; -import {P2PMessageProcessor} from "@/ts/message_handlers/P2PMessageProcessor"; +import type {MessageModel, RoomModel} from "@/ts/types/model"; +import {MessageStatusInner} from "@/ts/types/model"; +import type {MessagesInfo} from "@/ts/types/messages/p2p/dto/message.info"; +import {messageModelToP2p, p2pMessageToModel} from "@/ts/types/converters"; + +import type {MessageHelper} from "@/ts/message_handlers/MessageHelper"; +import loggerFactory from "@/ts/instances/loggerFactory"; +import type Subscription from "@/ts/classes/Subscription"; +import { + P2PSubscribe, + Subscribe, +} from "@/ts/utils/pubsub"; +import type {SetMessageStatusWsInMessage} from "@common/ws/message/set.message.status"; +import {SetMessageStatusWsInBody} from "@common/ws/message/set.message.status"; +import {SendSetMessagesStatusMessageBody} from "@/ts/types/messages/inner/send.set.messages.status"; +import type { + SendSetMessagesStatusInnerSystemMessage, +} from "@/ts/types/messages/inner/send.set.message.status"; +import { + SendSetMessagesStatusInnerSystemBody, +} from "@/ts/types/messages/inner/send.set.message.status"; +import {SetMessageStatusP2pMessage} from "@/ts/types/messages/p2p/set.message.status"; import type { - HandlerType, - HandlerTypes, -} from "@/ts/types/messages/baseMessagesInterfaces"; + ExchangeMessageInfo1RequestP2pMessage, +} from "@/ts/types/messages/p2p/exchange.message.info"; +import AbstractMessageProcessor from "@/ts/message_handlers/AbstractMessageProcessor"; +import type {DefaultP2pMessage} from "@/ts/types/messages/p2p"; import type { - ConfirmReceivedP2pMessage, - ConfirmSetMessageStatusRequest, - DefaultP2pMessage, - ExchangeMessageInfoRequest, + RequestWsOutMessage, + ResponseWsInMessage, +} from "@common/ws/common"; +import { ExchangeMessageInfoResponse, ExchangeMessageInfoResponse2, ExchangeMessageInfoResponse3, - P2PHandlerType, - P2PHandlerTypes, - SendNewP2PMessage, - SetMessageStatusRequest, + SendNewP2PMessage } from "@/ts/types/messages/p2pMessages"; -import type { - MessageModel, - RoomModel, -} from "@/ts/types/model"; -import type { - MessageP2pDto, - MessagesInfo, -} from "@/ts/types/messages/p2pDto"; +import {MessageP2pDto} from "@/ts/types/messages/p2p/dto/message"; import { - messageModelToP2p, - p2pMessageToModel, -} from "@/ts/types/converters"; -import type { - SendSetMessagesStatusMessage, - SyncP2PMessage, -} from "@/ts/types/messages/innerMessages"; -import type {MessageHelper} from "@/ts/message_handlers/MessageHelper"; -import loggerFactory from "@/ts/instances/loggerFactory"; -import type Subscription from "@/ts/classes/Subscription"; + CheckTransferDestroyBody, + CheckTransferDestroyMessage +} from "@/ts/types/messages/inner/check.transfer.destroy"; + -export default abstract class MessagePeerConnection extends AbstractPeerConnection implements MessageSupplier { +export default abstract class MessagePeerConnection extends AbstractPeerConnection { connectedToRemote: boolean = true; - protected readonly handlers: HandlerTypes = { - sendRtcData: > this.sendRtcData, - checkDestroy: > this.checkDestroy, - syncP2pMessage: > this.syncP2pMessage, - sendSetMessagesStatus: > this.sendSetMessagesStatus, - }; + protected readonly callBacks: Record = {}; - protected status: "inited" | "not_inited" = "not_inited"; + // protected readonly handlers: HandlerTypes = { + // syncP2pMessage: > this.syncP2pMessage, + // sendSetMessagesStatus: > this.sendSetMessagesStatus, + // }; - private readonly p2pHandlers: P2PHandlerTypes = { - exchangeMessageInfoRequest: > this.exchangeMessageInfoRequest, - sendNewP2PMessage: > this.sendNewP2PMessage, - setMessageStatus: > this.setMessageStatus, - }; + protected status: "inited" | "not_inited" = "not_inited"; private readonly messageProc: P2PMessageProcessor; @@ -71,7 +78,7 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti roomId: number, connId: string, opponentWsId: string, - wsHandler: WsHandler, + wsHandler: WsApi, store: DefaultStore, userId: number, messageHelper: MessageHelper, @@ -88,9 +95,6 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti return this.opponentUserId === this.store.myId; } - private get isChannelOpened(): boolean { - return this.sendChannel?.readyState === "open"; - } private get messages(): MessageModel[] { return Object.values(this.room.messages); @@ -100,31 +104,29 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti return this.store.roomsDict[this.roomId]; } - public async sendSetMessagesStatus(payload: SendSetMessagesStatusMessage) { - const responseToRequest: SetMessageStatusRequest = { + @Subscribe() + public async sendSetMessagesStatus(payload: SendSetMessagesStatusInnerSystemBody) { + await this.messageProc.sendToServerAndAwait({ action: "setMessageStatus", - messagesIds: payload.messageIds, - status: "read", - }; - await this.messageProc.sendToServerAndAwait(responseToRequest); + data: { + messagesIds: payload.messageIds, + status: MessageStatus.READ, + }, + }); this.store.setMessagesStatus({ roomId: this.roomId, - status: "read", + status: MessageStatus.READ, messagesIds: payload.messageIds, }); } - public async setMessageStatus(m: SetMessageStatusRequest) { + @P2PSubscribe() + public async setMessageStatus(m: SetMessageStatusP2pMessage) { this.store.setMessagesStatus({ roomId: this.roomId, - status: m.status, - messagesIds: m.messagesIds, + status: m.data.status, + messagesIds: m.data.messagesIds, }); - const response: ConfirmSetMessageStatusRequest = { - action: "confirmSetMessageStatusRequest", - resolveCbId: m.cbId, - }; - this.messageProc.sendToServer(response); } public getOpponentUserId() { @@ -156,13 +158,14 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti if (payload.message.userId !== this.store.myId) { const isRead = this.store.isCurrentWindowActive && this.store.activeRoomId === this.roomId; if (isRead) { - response.status = "read"; + response.status = MessageStatus.READ; } } this.messageProc.sendToServer(response); } - public async syncP2pMessage(payload: SyncP2PMessage) { + @Subscribe() + public async syncP2pMessage(payload: SyncP2PMessageBody) { if (this.isChannelOpened) { this.logger.debug("Syncing message {}", payload.id)(); const message: SendNewP2PMessage = { @@ -187,8 +190,8 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti } } - public checkDestroy() { - + @Subscribe() + public checkDestroy(body: CheckTransferDestroyBody) { /* * Destroy only if user has left this room, if he's offline but connections is stil in progress, * maybe he has jost connection to server but not to us @@ -200,7 +203,8 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti } } - public async exchangeMessageInfoRequest(payload: ExchangeMessageInfoRequest) { + @P2PSubscribe() + public async exchangeMessageInfo(payload: ExchangeMessageInfo1RequestP2pBody) { if (this.syncMessageLock) { this.logger.error("oops we already acquired lock, going to syncanyway"); } @@ -243,7 +247,7 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti action: "exchangeMessageInfoResponse3", resolveCbId: a.cbId, }; - this.messageProc.sendToServer(confirmationThatReceived); + this.messageProc.sendToServer<>(confirmationThatReceived); } finally { this.syncMessageLock = false; } @@ -256,7 +260,7 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti } try { this.syncMessageLock = true; - await this.exchangeMessageInfo(); + await this.startMessageInfoExchange(); } catch (e) { this.logger.error("Can't send messages because {}", e)(); } finally { @@ -269,70 +273,59 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti this.messageProc.onDropConnection("data channel lost"); } - public getWsConnectionId(): string { - return this.wsHandler.getWsConnectionId(); - } + public resolveCBifItsThere(data: DefaultP2pMessage): boolean { - public sendRawTextToServer(message: string): boolean { - if (this.isChannelOpened) { - this.sendChannel!.send(message); - return true; - } - return false; } - protected setupEvents() { - this.sendChannel!.onmessage = this.onChannelMessage.bind(this); - this.sendChannel!.onopen = () => { - this.logger.log("Channel opened")(); - if (this.getWsConnectionId() > this.opponentWsId) { - this.syncMessages(); - } - this.store.addLiveConnectionToRoom({ - connection: this.opponentWsId, - roomId: this.roomId, - }); - }; - this.sendChannel!.onclose = () => { - this.logger.log("Closed channel ")(); - // This.syncMessageLock = false; // just for the case, not nessesary - this.messageProc.onDropConnection("Data channel closed"); - if (this.store.userInfo) { - // Otherwise we logged out - this.store.removeLiveConnectionToRoom({ - connection: this.opponentWsId, - roomId: this.roomId, - }); + public async sendToServerAndAwaitData, RES extends ResponseWsInMessage>(message: Omit): Promise { + return new Promise((resolve, reject) => { + (message as any).cbId = ++this.uniquePositiveMessageId; + const jsonMessage = this.getJsonMessage(message); + this.callBacks[(message as any).cbId!] = { + resolve, + reject, + all: true, + }; + const isSent = this.sendRawTextToServer(jsonMessage); + if (isSent) { + this.logData(this.loggerOut, jsonMessage, message)(); } - }; + }); } + protected onChannelMessage(event: MessageEvent) { - const data: DefaultP2pMessage = this.messageProc.parseMessage(event.data) as unknown as DefaultP2pMessage; - if (data) { - const cb = this.messageProc.resolveCBifItsThere(data); - if (!cb) { - const handler: P2PHandlerType = this.p2pHandlers[data.action] as P2PHandlerType; - if (handler) { - handler.bind(this)(data); - } else { - this.logger.error("{} can't find handler for {}, available handlers {}. Message: {}", this.constructor.name, data.action, Object.keys(this.p2pHandlers), data)(); - } + const data = this.messageProc.parseMessage(event.data); + if (!data) { + throw Error("WTF"); + } + if (data.resolveCbId) { + this.callBacks[data.resolveCbId].resolve(data); + delete this.callBacks[data.resolveCbId]; + } else if (data.action) { + const handler = this.__p2p_handlers[data.action]; + if (handler) { + handler.bind(this)(data); + } else { + throw Error(`Invalid handler ${handler} for message ${event.data}`); } + } else { + throw Error(`Invalid message ${event.data}`); } } - private async exchangeMessageInfo() { + private async startMessageInfoExchange() { if (this.isChannelOpened) { const mI: MessagesInfo = this.messages.reduce((p, c) => { p[c.id] = c.edited ?? 0; // (undefied|null) ?? 0 === 0 return p; }, {}); - const message: ExchangeMessageInfoRequest = { + const response: ExchangeMessageInfoResponse = await this.messageProc.sendToServerAndAwait({ action: "exchangeMessageInfoRequest", - messagesInfo: mI, - }; - const response: ExchangeMessageInfoResponse = await this.messageProc.sendToServerAndAwait(message); + data: { + messagesInfo: mI, + }, + }); // Got exchangeMessageInfoResponse this.saveMessagesDtoToStore(response.messages); const responseMessages: MessageP2pDto[] = response.requestMessages.map( @@ -352,7 +345,7 @@ export default abstract class MessagePeerConnection extends AbstractPeerConnecti private markAsReadSentMessages(responseMessages: MessageP2pDto[]) { if (!this.isConnectedToMyAnotherDevices) { - const isNotRead: number[] = responseMessages.map((m) => m.id).filter((id) => this.room.messages[id].status === "sending"); + const isNotRead: number[] = responseMessages.map((m) => m.id).filter((id) => this.room.messages[id].status === MessageStatusInner.SENDING); if (isNotRead.length > 0) { this.store.markMessageAsSent({ messagesId: isNotRead, diff --git a/frontend/src/ts/webrtc/message/MessageTransferHandler.ts b/frontend/src/ts/webrtc/message/MessageTransferHandler.ts index 8590847f4..626a64a9e 100644 --- a/frontend/src/ts/webrtc/message/MessageTransferHandler.ts +++ b/frontend/src/ts/webrtc/message/MessageTransferHandler.ts @@ -1,34 +1,33 @@ +import {MessageStatus} from "@common/model/enum/message.status"; +import type {SendSetMessagesStatusMessage} from "@/ts/types/messages/inner/send.set.messages.status"; +import type {SyncP2PInnerSystemMessage} from "@/ts/types/messages/inner/sync.p2p"; import BaseTransferHandler from "@/ts/webrtc/BaseTransferHandler"; -import type { - MessageSender, - UserIdConn, -} from "@/ts/types/types"; +import type {MessageSender, UserIdConn} from "@/ts/types/types"; import type {RoomModel} from "@/ts/types/model"; import MessageSenderPeerConnection from "@/ts/webrtc/message/MessageSenderPeerConnection"; import MessageReceiverPeerConnection from "@/ts/webrtc/message/MessageReceiverPeerConnection"; -import type WsHandler from "@/ts/message_handlers/WsHandler"; +import type WsApi from "@/ts/message_handlers/WsApi"; import type NotifierHandler from "@/ts/classes/NotificationHandler"; import type {DefaultStore} from "@/ts/classes/DefaultStore"; import Subscription from "@/ts/classes/Subscription"; -import type { - SendSetMessagesStatusMessage, - SyncP2PMessage, -} from "@/ts/types/messages/innerMessages"; -import type {HandlerTypes} from "@/ts/types/messages/baseMessagesInterfaces"; + + import type {MessageHelper} from "@/ts/message_handlers/MessageHelper"; +import type {SendSetMessagesStatusInnerSystemMessage} from "@/ts/types/messages/inner/send.set.message.status"; + /** * * https://drive.google.com/file/d/1BCtFNNWprfobqQlG4n2lPyWEqroi7nJh/view */ export default class MessageTransferHandler extends BaseTransferHandler implements MessageSender { - protected readonly handlers: HandlerTypes = {}; + // protected readonly handlers: HandlerTypes = {}; private state: "initing" | "not_inited" | "ready" = "not_inited"; private readonly messageHelper: MessageHelper; - public constructor(roomId: number, wsHandler: WsHandler, notifier: NotifierHandler, store: DefaultStore, messageHelper: MessageHelper, sub: Subscription) { + public constructor(roomId: number, wsHandler: WsApi, notifier: NotifierHandler, store: DefaultStore, messageHelper: MessageHelper, sub: Subscription) { super(roomId, wsHandler, notifier, store, sub); this.messageHelper = messageHelper; } @@ -62,7 +61,7 @@ export default class MessageTransferHandler extends BaseTransferHandler implemen async syncMessage(roomId: number, messageId: number): Promise { this.messageHelper.processAnyMessage(); if (this.state === "ready") { - const payload: SyncP2PMessage = { + const payload: SyncP2PInnerSystemMessage = { action: "syncP2pMessage", handler: Subscription.allPeerConnectionsForTransfer(this.connectionId!), id: messageId, @@ -181,14 +180,15 @@ export default class MessageTransferHandler extends BaseTransferHandler implemen public async markMessagesInCurrentRoomAsRead(roomId: number, messageIds: number[]) { this.messageHelper.processAnyMessage(); if (this.state === "ready") { - const payload: SendSetMessagesStatusMessage = { + this.sub.notify({ action: "sendSetMessagesStatus", handler: Subscription.allPeerConnectionsForTransfer(this.connectionId!), - messageIds, - status: "read", + data: { + messageIds, + status: MessageStatus.READ, + }, allowZeroSubscribers: true, - }; - this.sub.notify(payload); + }); } } diff --git a/frontend/src/vue/App.vue b/frontend/src/vue/App.vue index 1548895e1..76a5344b1 100644 --- a/frontend/src/vue/App.vue +++ b/frontend/src/vue/App.vue @@ -5,16 +5,16 @@ - + + diff --git a/frontend/src/vue/singup/FacebookAuth.vue b/frontend/src/vue/auth/FacebookAuth.vue similarity index 96% rename from frontend/src/vue/singup/FacebookAuth.vue rename to frontend/src/vue/auth/FacebookAuth.vue index ff5ea66d0..1f9fbd329 100644 --- a/frontend/src/vue/singup/FacebookAuth.vue +++ b/frontend/src/vue/auth/FacebookAuth.vue @@ -12,11 +12,7 @@ diff --git a/frontend/src/vue/chat/chatbox/SearchMessages.vue b/frontend/src/vue/chat/chatbox/SearchMessages.vue index 754585ce1..e60222c22 100644 --- a/frontend/src/vue/chat/chatbox/SearchMessages.vue +++ b/frontend/src/vue/chat/chatbox/SearchMessages.vue @@ -25,13 +25,7 @@ diff --git a/frontend/src/vue/chat/chatbox/VideoContainer.vue b/frontend/src/vue/chat/chatbox/VideoContainer.vue index c46fe3c34..a7014ddf7 100644 --- a/frontend/src/vue/chat/chatbox/VideoContainer.vue +++ b/frontend/src/vue/chat/chatbox/VideoContainer.vue @@ -61,14 +61,7 @@ diff --git a/frontend/src/vue/pages/MainPage.vue b/frontend/src/vue/pages/MainPage.vue index 181e548b4..5cf587085 100644 --- a/frontend/src/vue/pages/MainPage.vue +++ b/frontend/src/vue/pages/MainPage.vue @@ -19,14 +19,8 @@ - - diff --git a/frontend/src/vue/pages/RoomSettings.vue b/frontend/src/vue/pages/RoomSettings.vue index 048f710ac..1d61c878f 100644 --- a/frontend/src/vue/pages/RoomSettings.vue +++ b/frontend/src/vue/pages/RoomSettings.vue @@ -42,7 +42,7 @@ /> - {{ oldAdmin.user }} + {{ oldAdmin.username }} This room doesn't have an admin @@ -115,14 +115,11 @@ diff --git a/frontend/src/vue/parts/CreateRoom.vue b/frontend/src/vue/parts/CreateRoom.vue index 00f5d2b89..194341781 100644 --- a/frontend/src/vue/parts/CreateRoom.vue +++ b/frontend/src/vue/parts/CreateRoom.vue @@ -61,22 +61,12 @@ diff --git a/frontend/src/vue/parts/Growls.vue b/frontend/src/vue/parts/Growls.vue index 7d665897f..9af33c58d 100644 --- a/frontend/src/vue/parts/Growls.vue +++ b/frontend/src/vue/parts/Growls.vue @@ -8,11 +8,7 @@ - - diff --git a/frontend/src/vue/ui/AppCheckbox.vue b/frontend/src/vue/ui/AppCheckbox.vue index 28d0ab775..2f3e93edd 100644 --- a/frontend/src/vue/ui/AppCheckbox.vue +++ b/frontend/src/vue/ui/AppCheckbox.vue @@ -11,13 +11,7 @@