├── .gitattributes ├── .idea ├── .gitignore ├── angular-send-file-peer-to-peer.iml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── vcs.xml ├── README.md ├── f2f-backend ├── .gitignore ├── Dockerfile ├── README.md ├── build.gradle ├── docker-compose.yml ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── microservices │ └── activemq │ │ ├── conf │ │ ├── activemq.xml │ │ ├── activemq1.xml │ │ ├── broker-localhost.cert │ │ ├── broker.ks │ │ ├── broker.ts │ │ ├── client.ks │ │ ├── client.ts │ │ ├── credentials-enc.properties │ │ ├── credentials.properties │ │ ├── groups.properties │ │ ├── java.security │ │ ├── jetty-realm.properties │ │ ├── jetty.xml │ │ ├── jmx.access │ │ ├── jmx.password │ │ ├── log4j.properties │ │ ├── logging.properties │ │ ├── login.config │ │ └── users.properties │ │ └── data │ │ ├── activemq.log │ │ ├── activemq.log.1 │ │ ├── activemq.log.2 │ │ ├── activemq.log.3 │ │ ├── activemq.log.4 │ │ ├── activemq.log.5 │ │ ├── activemq.pid │ │ ├── audit.log │ │ └── kahadb │ │ ├── db-1.log │ │ ├── db.data │ │ ├── db.redo │ │ └── lock ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── f2f │ │ │ └── backend │ │ │ ├── BackendApplication.java │ │ │ ├── ServletInitializer.java │ │ │ ├── SignalingConfiguration.java │ │ │ ├── SignalingController.java │ │ │ ├── SignalingService.java │ │ │ ├── ds │ │ │ ├── EPayloadState.java │ │ │ ├── EPeerState.java │ │ │ ├── FilePart.java │ │ │ ├── FilePartId.java │ │ │ ├── Peer.java │ │ │ ├── SignalingChannel.java │ │ │ └── Stamp.java │ │ │ ├── dto │ │ │ ├── GetPartCompletedReq.java │ │ │ ├── InitializeChannelReq.java │ │ │ ├── InitializeChannelRes.java │ │ │ ├── PeerStatusUpdateReq.java │ │ │ └── RegisterFileReq.java │ │ │ ├── repo │ │ │ ├── FilePartRepository.java │ │ │ ├── PeerRepository.java │ │ │ └── SignalingChannelRepository.java │ │ │ └── utilities │ │ │ ├── AuthorizationValid.java │ │ │ ├── AuthorizationValidAspect.java │ │ │ ├── Base58.java │ │ │ ├── Constant.java │ │ │ ├── FileIdGenerator.java │ │ │ └── PeerDetails.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── f2f │ └── backend │ └── BackendApplicationTests.java └── f2f-frontend ├── .browserslistrc ├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── README.md ├── angular.json ├── docker-compose.yml ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── f2f.nhvu95.com.conf ├── karma.conf.js ├── nginx.conf ├── package-lock.json ├── package.json ├── postcss.config.js ├── scully ├── plugins │ ├── plugin.js │ ├── plugin.js.map │ └── plugin.ts └── tsconfig.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── interceptor │ │ └── http.interceptor.ts │ ├── receiver │ │ ├── receiver.action.ts │ │ ├── receiver.component.html │ │ ├── receiver.component.scss │ │ ├── receiver.component.ts │ │ ├── receiver.module.ts │ │ ├── receiver.selectors.ts │ │ └── receiver.state.ts │ ├── sender │ │ ├── sender.action.ts │ │ ├── sender.component.html │ │ ├── sender.component.scss │ │ ├── sender.component.ts │ │ ├── sender.module.ts │ │ ├── sender.selector.ts │ │ └── sender.state.ts │ ├── services │ │ ├── constant.ts │ │ ├── rx-stomp-bridge.service.ts │ │ ├── rx-stomp.config.ts │ │ ├── signaling-receiver.service.ts │ │ ├── signaling-sender.service.ts │ │ └── signaling.service.ts │ └── shared │ │ ├── app.action.ts │ │ ├── app.model.ts │ │ ├── app.selector.ts │ │ ├── app.state.ts │ │ ├── debug.decorator.ts │ │ ├── file-name.pipe.ts │ │ ├── shared-app.module.ts │ │ ├── shared-app.service.ts │ │ └── yes-no-dialog │ │ └── yes-no-dialog.component.ts ├── assets │ ├── .gitkeep │ └── images │ │ ├── IMG_1148.JPG │ │ └── icons_set.svg ├── environments │ ├── environment.prod.ts │ ├── environment.ts │ └── envrionment.model.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json ├── tslint.json └── webpack.config.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | *.1 linguist-language=text 2 | *.2 linguist-language=text 3 | *.3 linguist-language=text 4 | *.4 linguist-language=text 5 | *.5 linguist-language=text 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/angular-send-file-peer-to-peer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-send-file-peer-to-peer 2 | 3 | Demo: https://f2f.nhvu95.com 4 | 5 | * Frontend : Angular 6 | * Backend: SpringBoot 7 | 8 | :leaves: This is a sending file website that allows users to send files directly between browsers without storing them in any storage services. 9 | 10 | :herb: It can support sending a big file which amounts to **80GB** which no free file-sending website available on the internet can support. 11 | 12 | :point_right: This is a project which I use to beautify my CV, so please give me a :star: if you think it useful. 13 | 14 | :crying_cat_face: Known limitation: If you send a sharing link to many friends, the last friend who gets this link could cancel the current connection. 15 | 16 | :european_castle: This is overall structure, the more detailed README.MD files are inside the subfolder. You can contact me if you need any help, I am really glad. 17 | 18 | :email: email: nhvu.95@gmail.com 19 | :earth_asia: website: https://nhvu95.com/ 20 | 21 | ![Image](https://i.imgur.com/fFc0AdH.png) 22 | 23 | -------------------------------------------------------------------------------- /f2f-backend/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | bin/ 16 | !**/src/main/**/bin/ 17 | !**/src/test/**/bin/ 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | out/ 25 | !**/src/main/**/out/ 26 | !**/src/test/**/out/ 27 | 28 | ### NetBeans ### 29 | /nbproject/private/ 30 | /nbbuild/ 31 | /dist/ 32 | /nbdist/ 33 | /.nb-gradle/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /f2f-backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11-jre-slim 2 | ARG JAR_FILE=./build/libs/f2f-backend.jar 3 | COPY ${JAR_FILE} f2f-backend.jar 4 | ENTRYPOINT ["java","-jar","/f2f-backend.jar"] -------------------------------------------------------------------------------- /f2f-backend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Gradle documentation](https://docs.gradle.org) 7 | * [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.7.5/gradle-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.7.5/gradle-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#web) 10 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.7.5/reference/htmlsingle/#data.sql.jpa-and-spring-data) 11 | 12 | ### Guides 13 | The following guides illustrate how to use some features concretely: 14 | 15 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 16 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 17 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 18 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 19 | 20 | ## Self host backend 21 | ### Build JAR 22 | `gradlew build -x test` 23 | 24 | ### Build docker image 25 | `docker build -t f2f-backend .` 26 | 27 | ### Create docker network name tunnel 28 | `docker network create tunnel` 29 | 30 | ### Build docker container 31 | `docker compose up -d` 32 | 33 | ### Config activemq password 34 | The default activemq's password is `yourpassword` 35 | If you want to change, please open activemq terminal in docker 36 | 37 | `bin/activemq encrypt --password activemq --input yourpassword` 38 | 39 | Copy Encrypted password and replace in `client.password` inside .\microservices\activemq\credentials-enc.properties 40 | 41 | then restart activemq in docker. However, please careful that we only config the password one time 42 | 43 | 44 | ### Additional Links 45 | These additional references should also help you: 46 | 47 | * [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) 48 | 49 | -------------------------------------------------------------------------------- /f2f-backend/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '2.7.5' 4 | id 'io.spring.dependency-management' version '1.0.15.RELEASE' 5 | } 6 | 7 | group = 'com.f2f' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '11' 10 | 11 | configurations { 12 | compileOnly { 13 | extendsFrom annotationProcessor 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | // https://mvnrepository.com/artifact/org.hibernate/hibernate-core 23 | // implementation group: 'org.hibernate', name: 'hibernate-core', version: '5.6.10.Final' 24 | // implementation group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.3.7.Final' 25 | implementation "org.mindrot:jbcrypt:0.4" 26 | 27 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 28 | implementation 'org.springframework.boot:spring-boot-starter-web' 29 | compileOnly 'org.projectlombok:lombok' 30 | runtimeOnly 'org.postgresql:postgresql' 31 | annotationProcessor 'org.projectlombok:lombok' 32 | // providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' 33 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 34 | } 35 | 36 | bootJar { 37 | archiveName = 'f2f-backend.jar' 38 | } 39 | 40 | tasks.named('test') { 41 | useJUnitPlatform() 42 | } 43 | -------------------------------------------------------------------------------- /f2f-backend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | name: f2f-backend 3 | services: 4 | db: 5 | image: postgres 6 | restart: always 7 | environment: 8 | POSTGRES_USER: postgres 9 | POSTGRES_PASSWORD: postgres 10 | ports: 11 | - '5432:5432' 12 | restart: unless-stopped 13 | pgadmin: 14 | container_name: pgadmin_container 15 | image: dpage/pgadmin4 16 | environment: 17 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-nhvu95@gmail.com} 18 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} 19 | PGADMIN_CONFIG_SERVER_MODE: 'False' 20 | ports: 21 | - '5232:80' 22 | restart: unless-stopped 23 | activemq: 24 | restart: unless-stopped 25 | ports: 26 | - '8161:8161' 27 | - '5672:5672' 28 | volumes: 29 | - './microservices/activemq/conf:/opt/activemq/conf' 30 | - './microservices/activemq/data:/opt/activemq/data' 31 | environment: 32 | - ACTIVEMQ_ENCRYPTION_PASSWORD=activemq 33 | image: rmohr/activemq 34 | f2f-backend: 35 | image: f2f-backend:latest 36 | restart: unless-stopped 37 | ports: 38 | - '5231:5231' 39 | networks: 40 | default: 41 | name: tunnel 42 | external: true -------------------------------------------------------------------------------- /f2f-backend/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /f2f-backend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /f2f-backend/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /f2f-backend/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/activemq.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 80 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/activemq1.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | file:${activemq.conf}/credentials.properties 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 81 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | 95 | 96 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/broker-localhost.cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/conf/broker-localhost.cert -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/broker.ks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/conf/broker.ks -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/broker.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/conf/broker.ts -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/client.ks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/conf/client.ks -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/client.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/conf/client.ts -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/credentials-enc.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | # Defines credentials that will be used by components (like web console) to access the broker 19 | 20 | activemq.username=system 21 | activemq.password=ENC(mYRkg+4Q4hua1kvpCCI2hg==) 22 | guest.password=ENC(Cf3Jf3tM+UrSOoaKU50od5CuBa8rxjoL) 23 | client.password=ENC(NKh3cC5CKJ6kTJfsAqcVdn+xpIffVNJP) 24 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/credentials.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | # Defines credentials that will be used by components (like web console) to access the broker 19 | 20 | activemq.username=system 21 | activemq.password=manager 22 | guest.password=password -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/groups.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | admins=system,sslclient,client,broker1,broker2 19 | tempDestinationAdmins=system,user,sslclient,client,broker1,broker2 20 | users=system,user,sslclient,client,broker1,broker2 21 | guests=guest -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/java.security: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | ocsp.enable=true 19 | ocsp.responderURL=http://ocsp.example.net:80 -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/jetty-realm.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | # Defines users that can access the web (console, demo, etc.) 19 | # username: password [,rolename ...] 20 | admin: admin, admin 21 | user: user, user -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/jetty.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | index.html 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/jmx.access: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | admin readwrite -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/jmx.password: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | admin activemq -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/log4j.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | # 19 | # This file controls most of the logging in ActiveMQ which is mainly based around 20 | # the commons logging API. 21 | # 22 | log4j.rootLogger=INFO, console, logfile 23 | log4j.logger.org.apache.activemq.spring=WARN 24 | log4j.logger.org.apache.activemq.web.handler=WARN 25 | log4j.logger.org.springframework=WARN 26 | log4j.logger.org.apache.xbean=WARN 27 | log4j.logger.org.apache.camel=INFO 28 | log4j.logger.org.eclipse.jetty=WARN 29 | 30 | # When debugging or reporting problems to the ActiveMQ team, 31 | # comment out the above lines and uncomment the next. 32 | 33 | #log4j.rootLogger=DEBUG, logfile, console 34 | 35 | # Or for more fine grained debug logging uncomment one of these 36 | #log4j.logger.org.apache.activemq=DEBUG 37 | #log4j.logger.org.apache.camel=DEBUG 38 | 39 | # Console appender 40 | log4j.appender.console=org.apache.log4j.ConsoleAppender 41 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 42 | log4j.appender.console.layout.ConversionPattern=%5p | %m%n 43 | log4j.appender.console.threshold=INFO 44 | 45 | # File appender 46 | log4j.appender.logfile=org.apache.log4j.RollingFileAppender 47 | log4j.appender.logfile.file=${activemq.data}/activemq.log 48 | log4j.appender.logfile.maxFileSize=1024KB 49 | log4j.appender.logfile.maxBackupIndex=5 50 | log4j.appender.logfile.append=true 51 | log4j.appender.logfile.layout=org.apache.log4j.PatternLayout 52 | log4j.appender.logfile.layout.ConversionPattern=%d | %-5p | %m | %c | %t%n 53 | # use some of the following patterns to see MDC logging data 54 | # 55 | # %X{activemq.broker} 56 | # %X{activemq.connector} 57 | # %X{activemq.destination} 58 | # 59 | # e.g. 60 | # 61 | # log4j.appender.logfile.layout.ConversionPattern=%d | %-20.20X{activemq.connector} | %-5p | %m | %c | %t%n 62 | 63 | log4j.throwableRenderer=org.apache.log4j.EnhancedThrowableRenderer 64 | 65 | ########### 66 | # Audit log 67 | ########### 68 | 69 | log4j.additivity.org.apache.activemq.audit=false 70 | log4j.logger.org.apache.activemq.audit=INFO, audit 71 | 72 | log4j.appender.audit=org.apache.log4j.RollingFileAppender 73 | log4j.appender.audit.file=${activemq.data}/audit.log 74 | log4j.appender.audit.maxFileSize=1024KB 75 | log4j.appender.audit.maxBackupIndex=5 76 | log4j.appender.audit.append=true 77 | log4j.appender.audit.layout=org.apache.log4j.PatternLayout 78 | log4j.appender.audit.layout.ConversionPattern=%-5p | %m | %t%n -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/logging.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | # 19 | # This file controls logging which is done over the java.logging API 20 | # 21 | 22 | #handlers = java.util.logging.ConsoleHandler 23 | #java.util.logging.ConsoleHandler.level=INFO 24 | #java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 25 | #.level=INFO# -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/login.config: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | activemq { 18 | org.apache.activemq.jaas.PropertiesLoginModule required 19 | org.apache.activemq.jaas.properties.user="users.properties" 20 | org.apache.activemq.jaas.properties.group="groups.properties"; 21 | }; -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/conf/users.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | 18 | system=manager 19 | user=password 20 | guest=password 21 | sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/activemq.pid: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/audit.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/data/audit.log -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/kahadb/db-1.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/data/kahadb/db-1.log -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/kahadb/db.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/data/kahadb/db.data -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/kahadb/db.redo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/data/kahadb/db.redo -------------------------------------------------------------------------------- /f2f-backend/microservices/activemq/data/kahadb/lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-backend/microservices/activemq/data/kahadb/lock -------------------------------------------------------------------------------- /f2f-backend/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'backend' 2 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/BackendApplication.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableAutoConfiguration 9 | public class BackendApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(BackendApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(BackendApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/SignalingConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class SignalingConfiguration implements WebMvcConfigurer { 9 | 10 | // I disabled CORS here, and use CORS it on my Cloudflare tunnel. 11 | @Override 12 | public void addCorsMappings(CorsRegistry registry) { 13 | registry.addMapping("/**").allowedMethods("*"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/SignalingController.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.PutMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestHeader; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import com.f2f.backend.ds.FilePart; 21 | import com.f2f.backend.ds.Peer; 22 | import com.f2f.backend.ds.SignalingChannel; 23 | import com.f2f.backend.dto.GetPartCompletedReq; 24 | import com.f2f.backend.dto.InitializeChannelReq; 25 | import com.f2f.backend.dto.InitializeChannelRes; 26 | import com.f2f.backend.dto.PeerStatusUpdateReq; 27 | import com.f2f.backend.dto.RegisterFileReq; 28 | import com.f2f.backend.utilities.AuthorizationValid; 29 | import com.f2f.backend.utilities.PeerDetails; 30 | 31 | @RestController 32 | @RequestMapping("v1") 33 | public class SignalingController { 34 | 35 | SignalingService signalingServ; 36 | PeerDetails peerDetails; 37 | 38 | @Autowired 39 | SignalingController(SignalingService signalingServ, PeerDetails peerDetails) { 40 | this.signalingServ = signalingServ; 41 | this.peerDetails = peerDetails; 42 | } 43 | 44 | @GetMapping("/peer") 45 | public Long getPeerId() { 46 | return this.signalingServ.getPeerId(); 47 | } 48 | 49 | @PutMapping("/peer/{peerId}") 50 | public Peer setPeerStatus(@PathVariable("peerId") Long peerId, @RequestBody PeerStatusUpdateReq req) { 51 | return this.signalingServ.setPeerStatus(peerId, req.getFileId().orElse(null), req.getState()); 52 | } 53 | 54 | @PostMapping("/channel/initialize") 55 | public ResponseEntity intitalizeChannel(@RequestBody InitializeChannelReq req) { 56 | Long peerId = req.getPeerId(); 57 | if (peerId == null || !this.signalingServ.validatePeer(peerId)) { 58 | return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); 59 | } 60 | 61 | InitializeChannelRes res = this.signalingServ.initializeChannel(req); 62 | return new ResponseEntity<>(res, HttpStatus.OK); 63 | } 64 | 65 | @GetMapping("/channel/{channelId}/owner") 66 | @AuthorizationValid 67 | public ResponseEntity getChannelInfor(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization, 68 | @PathVariable("channelId") String encryptedChannelId) { 69 | Long channelId = this.peerDetails.getChannelId(); 70 | SignalingChannel channel = this.signalingServ.getChannelInformation(channelId); 71 | if (channel == null) { 72 | return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); 73 | } 74 | return new ResponseEntity<>(channel.getSourceOwnerId(), HttpStatus.OK); 75 | } 76 | 77 | @PostMapping("/channel/{channelId}/file") 78 | @AuthorizationValid 79 | public ResponseEntity registerFile(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization, 80 | @PathVariable("channelId") String encryptedChannelId, @RequestBody RegisterFileReq req) { 81 | Long peerId = this.peerDetails.getPeerId(); 82 | Long channelId = this.peerDetails.getChannelId(); 83 | FilePart entityFile = this.signalingServ.registerFile(channelId, peerId, req.totalPart); 84 | return new ResponseEntity<>(entityFile, HttpStatus.OK); 85 | } 86 | 87 | @GetMapping("/channel/{channelId}/file") 88 | @AuthorizationValid 89 | public ResponseEntity getNextPart(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization, 90 | @PathVariable("channelId") String encryptedChannelId, @RequestParam("fileId") Optional fileId) { 91 | Long peerId = this.peerDetails.getPeerId(); 92 | Long channelId = this.peerDetails.getChannelId(); 93 | FilePart nextPart = this.signalingServ.getPartForPeer(channelId, peerId, fileId.orElse(null)); 94 | if ( nextPart != null ) { 95 | return new ResponseEntity<>(nextPart, HttpStatus.OK); 96 | } else { 97 | return new ResponseEntity<>(nextPart, HttpStatus.NOT_FOUND); 98 | } 99 | } 100 | 101 | @PutMapping("/channel/{channelId}/file") 102 | @AuthorizationValid 103 | public ResponseEntity getPartComplete(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization, 104 | @PathVariable("channelId") String encryptedChannelId, @RequestBody GetPartCompletedReq req) { 105 | Long peerId = this.peerDetails.getPeerId(); 106 | Long channelId = this.peerDetails.getChannelId(); 107 | FilePart entityFile = this.signalingServ.getPartCompleted(channelId, peerId, req); 108 | return new ResponseEntity<>(entityFile, HttpStatus.OK); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/SignalingService.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import java.util.Optional; 10 | import java.util.Set; 11 | import java.util.UUID; 12 | import java.util.stream.Collectors; 13 | 14 | import org.mindrot.jbcrypt.BCrypt; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Service; 17 | 18 | import com.f2f.backend.ds.EPeerState; 19 | import com.f2f.backend.ds.FilePart; 20 | import com.f2f.backend.ds.Peer; 21 | import com.f2f.backend.ds.SignalingChannel; 22 | import com.f2f.backend.dto.GetPartCompletedReq; 23 | import com.f2f.backend.dto.InitializeChannelReq; 24 | import com.f2f.backend.dto.InitializeChannelRes; 25 | import com.f2f.backend.repo.FilePartRepository; 26 | import com.f2f.backend.repo.PeerRepository; 27 | import com.f2f.backend.repo.SignalingChannelRepository; 28 | import com.f2f.backend.utilities.Base58; 29 | 30 | @Service 31 | public class SignalingService { 32 | 33 | SignalingChannelRepository signalingRepo; 34 | PeerRepository peerRepo; 35 | FilePartRepository fileRepo; 36 | 37 | @Autowired 38 | SignalingService(SignalingChannelRepository signalingRepo, PeerRepository peerRepo, FilePartRepository fileRepo) { 39 | this.signalingRepo = signalingRepo; 40 | this.peerRepo = peerRepo; 41 | this.fileRepo = fileRepo; 42 | } 43 | 44 | public boolean validatePeer(Long peerId) { 45 | return peerRepo.existsById(peerId); 46 | } 47 | 48 | public String encryptChannelId(Long channelId) { 49 | String newId = String.format("%09d", channelId); 50 | return Base58.encode(newId.getBytes()); 51 | } 52 | 53 | public Long decryptChannelId(String encryptedChannelId) { 54 | String channelIdStr = new String(Base58.decode(encryptedChannelId)); 55 | Long channelId = Long.parseLong(channelIdStr.replaceFirst("^0+(?!$)", "")); 56 | return channelId; 57 | } 58 | 59 | public boolean validateChannel(Long channelId, String accessKey) { 60 | // Check channel exist 61 | Optional optChannel = signalingRepo.findById(channelId); 62 | if (optChannel.isEmpty()) { 63 | return false; 64 | } 65 | // Check access key is valid 66 | SignalingChannel channel = optChannel.get(); 67 | if (!BCrypt.checkpw(accessKey, channel.getAccessKey())) { 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | /** 75 | * STEP 1. Get Peer Id Each browser have 1 Peer Id 76 | * 77 | * @return 78 | */ 79 | public Long getPeerId() { 80 | Peer newPeer = this.peerRepo.save(new Peer()); 81 | return newPeer.getPeerId(); 82 | } 83 | 84 | /** 85 | * STEP 2. One of Peers(Browser) create a channel and let other Peers join in. 86 | * The channel Id will be convert from @NUMBER to a hash @String to prevent 87 | * hacker crawl 88 | * 89 | * @param req 90 | * @return 91 | */ 92 | public InitializeChannelRes initializeChannel(InitializeChannelReq req) { 93 | String accessKey = UUID.randomUUID().toString().split("-")[0]; 94 | String accessKeyEncrpt = BCrypt.hashpw(accessKey, BCrypt.gensalt(12)); 95 | SignalingChannel newChannel = this.signalingRepo.save(new SignalingChannel(req.peerId, accessKeyEncrpt)); 96 | return new InitializeChannelRes(encryptChannelId(newChannel.getChannelId()), accessKey); 97 | } 98 | 99 | /** 100 | * STEP 2.5. Get Channel information 101 | * 102 | * @param req 103 | * @return 104 | */ 105 | public SignalingChannel getChannelInformation(Long channelId) { 106 | Optional newChannel = this.signalingRepo.findById(channelId); 107 | return newChannel.orElse(null); 108 | } 109 | 110 | /** 111 | * STEP 3. One of Peers(Browser) register a shared file This file and all it 112 | * parts will be store as a record to the database 113 | * 114 | * @param req 115 | * @return the file part index 0 116 | */ 117 | public FilePart registerFile(Long channelId, Long peerId, Integer totalPart) { 118 | FilePart file = fileRepo.save(new FilePart(peerId, channelId, totalPart, 0, 0L)); 119 | List parts = new ArrayList(); 120 | for (int i = 1; i < totalPart; i++) { 121 | FilePart part = new FilePart(peerId, channelId, totalPart, i, 0L); 122 | parts.add(part); 123 | } 124 | if (parts.size() > 0) { 125 | fileRepo.saveAll(parts); 126 | } 127 | return file; 128 | } 129 | 130 | /** 131 | * STEP 4. Receiver(Browser) ask to download a file System will return the 132 | * suitable part and suitable sender which receiver should ask for. TODO: TODO: 133 | * 134 | * Hey! Last year browser cannot download files greater than 2Gb by using 135 | * FileSaver.js So I split a file into many small parts and manage them. 136 | * Nowaday, we have Streamsaver.js and WriteableStream. We can save file streams 137 | * directly. So all files send now have only one part with partIndex =0; 138 | * 139 | * @param channelId 140 | * @param peerId 141 | * @param fileId 142 | * @return 143 | */ 144 | public FilePart getPartForPeer(Long channelId, Long peerId, Long fileId) { 145 | 146 | List allParts; 147 | Map ownedParts; 148 | 149 | allParts = fileRepo.getAllFilePartByChannel(channelId); 150 | ownedParts = allParts.stream().filter(part -> part.getOwnerId().equals(peerId)) 151 | .collect(Collectors.toMap(FilePart::getSID, c -> c.getIndex())); 152 | 153 | // MINI_STEP 2: Get free peers 154 | SignalingChannel channel = signalingRepo.findById(channelId).get(); 155 | // MINI_STEP 3: Do we have any part is missing ? find it 156 | Set missingParts = new HashSet(); 157 | 158 | if (ownedParts.size() > 0) { 159 | allParts.forEach(filePart -> { 160 | if (filePart.getOwnerId().equals(peerId)) 161 | return; 162 | 163 | Long _fileId = filePart.getFileId(); 164 | int index = filePart.getIndex(); 165 | 166 | if (ownedParts.containsKey(_fileId + "-" + index)) { 167 | return; 168 | } 169 | 170 | missingParts.add(_fileId + "-" + index); 171 | }); 172 | 173 | } else { 174 | allParts.forEach(part -> { 175 | Long key = part.getFileId(); 176 | Integer index = part.getIndex(); 177 | missingParts.add(key + "-" + index); 178 | }); 179 | } 180 | 181 | // MINI_STEP 4: If there is no free peer. ask the source (Channel holder) 182 | FilePart missing = allParts.stream().filter(part -> { 183 | // Get all parts that belong to a sourceOwner - He always has what we need 184 | if (!part.getOwnerId().equals(channel.getSourceOwnerId())) { 185 | return false; 186 | } 187 | return true; 188 | }).filter(part -> { 189 | // Get first part that this peer is missing 190 | Long key = part.getFileId(); 191 | Integer index = part.getIndex(); 192 | return missingParts.contains(key + "-" + index); 193 | }).findFirst().orElse(null); 194 | 195 | return missing; 196 | } 197 | 198 | /** 199 | * Get All File in this channel 200 | * 201 | * @param channelId 202 | * @return 203 | */ 204 | public List getAllPartOrigin(Long channelId) { 205 | List allParts = fileRepo.getAllFilePartOrigin(channelId); 206 | return allParts.stream().filter(file -> file.getIndex().equals(0)).collect(Collectors.toList()); 207 | } 208 | 209 | /** 210 | * STEP 5. Receiver(Browser) send request to mark that it own this part And 211 | * others receiver can take from him 212 | * 213 | * @param channelId 214 | * @param peerId 215 | * @param req 216 | * @return 217 | */ 218 | public FilePart getPartCompleted(Long channelId, Long peerId, GetPartCompletedReq req) { 219 | FilePart newPart = new FilePart(peerId, channelId, req.getTotalPart(), req.getIndex(), req.getFileId()); 220 | return this.fileRepo.save(newPart); 221 | } 222 | 223 | /** 224 | * STEP 6. Set Peer Status 225 | * 226 | * @return 227 | */ 228 | public Peer setPeerStatus(Long peerId, Long fileId, EPeerState state) { 229 | Optional optPeer = this.peerRepo.findById(peerId); 230 | Peer peer = optPeer.orElse(null); 231 | switch (state) { 232 | case IDLE: 233 | peer.setReceiving(null); 234 | peer.setSending(null); 235 | break; 236 | case SENDING: 237 | peer.setSending(fileId); 238 | break; 239 | case TAKING: 240 | peer.setReceiving(fileId); 241 | break; 242 | default: 243 | break; 244 | } 245 | return this.peerRepo.save(peer); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/EPayloadState.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | public enum EPayloadState { 4 | NOT_AVAILABLE(0), 5 | TAKING(1), 6 | AVAILABLE(2); 7 | 8 | private final int value; 9 | 10 | EPayloadState(final int newValue) { 11 | value = newValue; 12 | } 13 | 14 | public int getValue() { return value; } 15 | } 16 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/EPeerState.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | public enum EPeerState { 4 | IDLE(0), 5 | TAKING(1), 6 | SENDING(2); 7 | 8 | private final int value; 9 | 10 | EPeerState(final int newValue) { 11 | value = newValue; 12 | } 13 | 14 | public int getValue() { return value; } 15 | } 16 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/FilePart.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.IdClass; 11 | import javax.persistence.SequenceGenerator; 12 | import javax.persistence.Table; 13 | 14 | import org.hibernate.annotations.GenericGenerator; 15 | 16 | import lombok.Data; 17 | 18 | @Entity 19 | @IdClass(FilePartId.class) 20 | @Table(name = "FilePart") 21 | @Data 22 | public class FilePart { 23 | public FilePart() { 24 | } 25 | 26 | public FilePart(Long ownerId, Long channelId, Integer totalPart, Integer index, Long origin) { 27 | super(); 28 | this.ownerId = ownerId; 29 | this.channelId = channelId; 30 | this.totalPart = totalPart; 31 | this.index = index; 32 | this.state = state.AVAILABLE; 33 | this.origin = origin; 34 | } 35 | 36 | @Id 37 | @Column(name = "file_id") 38 | @GenericGenerator(name = "file_id", strategy = "com.f2f.backend.utilities.FileIdGenerator") 39 | @GeneratedValue(generator = "file_id") 40 | Long fileId; 41 | 42 | @Id 43 | Long ownerId; 44 | 45 | @Id 46 | Integer index = 0; 47 | 48 | Long channelId; 49 | 50 | Long origin = 0L; // OL mean origin file part, Not OL mean copy file part 51 | 52 | EPayloadState state; 53 | Integer totalPart; 54 | 55 | public String getSID() { 56 | return this.fileId + "-" + this.index; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/FilePartId.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | @Data 8 | public class FilePartId implements Serializable { 9 | 10 | public FilePartId() { 11 | super(); 12 | } 13 | 14 | public FilePartId(Long fileId, Long ownerId, Integer index) { 15 | super(); 16 | this.fileId = fileId; 17 | this.ownerId = ownerId; 18 | this.index = index; 19 | } 20 | 21 | @SuppressWarnings("unused") 22 | private Long fileId; 23 | 24 | @SuppressWarnings("unused") 25 | private Long ownerId; 26 | 27 | @SuppressWarnings("unused") 28 | private Integer index; 29 | } -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/Peer.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.JoinColumn; 9 | import javax.persistence.ManyToOne; 10 | import javax.persistence.Table; 11 | 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | 15 | @Entity 16 | @Table(name = "Peer") 17 | @Getter 18 | @Setter 19 | public class Peer { 20 | 21 | @Id 22 | @Column(name = "peer_id") 23 | @GeneratedValue(strategy=GenerationType.IDENTITY) 24 | Long peerId; 25 | Long sending; 26 | Long receiving; 27 | 28 | @ManyToOne 29 | @JoinColumn(name = "channelId", nullable = true) 30 | private SignalingChannel channel; 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/SignalingChannel.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | import java.util.Set; 3 | 4 | import javax.persistence.Column; 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.OneToMany; 10 | import javax.persistence.Table; 11 | 12 | import lombok.Data; 13 | import lombok.Getter; 14 | import lombok.Setter; 15 | 16 | @Entity 17 | @Table(name="SignalingChannel") 18 | @Getter 19 | @Setter 20 | public class SignalingChannel { 21 | 22 | public SignalingChannel() { 23 | super(); 24 | } 25 | 26 | public SignalingChannel(Long sourceOwnerId, String accessKey) { 27 | this.sourceOwnerId = sourceOwnerId; 28 | this.accessKey = accessKey; 29 | } 30 | 31 | @Id 32 | @Column(name = "channel_id") 33 | @GeneratedValue(strategy=GenerationType.IDENTITY) 34 | Long channelId; 35 | String accessKey; 36 | Long sourceOwnerId; 37 | 38 | @OneToMany(mappedBy="peerId") 39 | Set actors; 40 | } 41 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/ds/Stamp.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.ds; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.SequenceGenerator; 10 | 11 | // JPA cannot create SEQ without Entity so I created this 12 | @Entity 13 | public class Stamp implements Serializable { 14 | @SequenceGenerator(name="file_id_seq", sequenceName="file_id_seq", initialValue = 1, allocationSize = 1) 15 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "file_id_seq") 16 | @Id 17 | private Long id; 18 | } -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/dto/GetPartCompletedReq.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GetPartCompletedReq { 7 | Long fileId; 8 | Integer index; 9 | Integer totalPart; 10 | } 11 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/dto/InitializeChannelReq.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class InitializeChannelReq { 7 | public Long peerId; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/dto/InitializeChannelRes.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class InitializeChannelRes { 7 | 8 | public InitializeChannelRes(String channelId, String accessKey) { 9 | this.channelId = channelId; 10 | this.accessKey = accessKey; 11 | } 12 | 13 | String channelId; 14 | String accessKey; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/dto/PeerStatusUpdateReq.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.dto; 2 | 3 | import java.util.Optional; 4 | 5 | import com.f2f.backend.ds.EPeerState; 6 | 7 | import lombok.Data; 8 | @Data 9 | public class PeerStatusUpdateReq { 10 | EPeerState state; 11 | Optional fileId; 12 | } 13 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/dto/RegisterFileReq.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class RegisterFileReq { 7 | public Integer totalPart; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/repo/FilePartRepository.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.repo; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.data.repository.query.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import com.f2f.backend.ds.FilePart; 11 | import com.f2f.backend.ds.FilePartId; 12 | 13 | @Repository 14 | public interface FilePartRepository extends JpaRepository { 15 | @Query(value = "SELECT * FROM file_part u WHERE u.channel_id = :channelId", nativeQuery = true) 16 | List getAllFilePartByChannel(@Param("channelId") Long channelId); 17 | 18 | @Query(value = "SELECT * FROM file_part u WHERE u.channel_id = :channelId AND u.origin == 0", nativeQuery = true) 19 | List getAllFilePartOrigin(@Param("channelId") Long channelId); 20 | 21 | @Query(value = "SELECT * FROM file_part u WHERE u.channel_id = :channelId AND u.owner_id != :peerId", nativeQuery = true) 22 | List getAllFilePartByChannelExcept(@Param("channelId") Long channelId, @Param("peerId") Long peerId); 23 | 24 | @Query(value = "SELECT * FROM file_part u WHERE u.channel_id = :channelId AND u.owner_id == :peerId", nativeQuery = true) 25 | List getAllFilePartByChannelOf(@Param("channelId") Long channelId, @Param("peerId") Long peerId); 26 | 27 | @Query(value = "SELECT * FROM file_part u WHERE u.file_id = :fileId AND u.owner_id != :peerId", nativeQuery = true) 28 | List getAllFilePartByFileIdExcept(@Param("fileId") Long fileId, @Param("peerId") Long peerId); 29 | 30 | @Query(value = "SELECT * FROM file_part u WHERE u.file_id = :fileId", nativeQuery = true) 31 | List getAllFilePartByFileId(@Param("fileId") Long fileId); 32 | } 33 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/repo/PeerRepository.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.repo; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import com.f2f.backend.ds.Peer; 7 | 8 | @Repository 9 | public interface PeerRepository extends JpaRepository { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/repo/SignalingChannelRepository.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.repo; 2 | import org.springframework.stereotype.Repository; 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.f2f.backend.ds.SignalingChannel; 6 | 7 | @Repository 8 | public interface SignalingChannelRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/AuthorizationValid.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | @Target(ElementType.METHOD) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface AuthorizationValid { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/AuthorizationValidAspect.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import org.aspectj.lang.ProceedingJoinPoint; 9 | import org.aspectj.lang.annotation.Around; 10 | import org.aspectj.lang.annotation.Aspect; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.servlet.HandlerMapping; 17 | 18 | import com.f2f.backend.SignalingService; 19 | 20 | @Aspect 21 | @Component 22 | public class AuthorizationValidAspect { 23 | 24 | @Autowired 25 | SignalingService signalingServ; 26 | @Autowired 27 | PeerDetails peerDetails; 28 | 29 | @Around("@annotation(AuthorizationValid)") 30 | public Object trace(ProceedingJoinPoint joinPoint) throws Throwable { 31 | Object[] args = joinPoint.getArgs(); 32 | String authorization = (String) args[0]; 33 | String encryptedChannelId = (String) args[1]; 34 | Long channelId = this.signalingServ.decryptChannelId(encryptedChannelId); 35 | 36 | if (authorization != null && authorization.toLowerCase().startsWith("basic")) { 37 | // Authorization: Basic base64credentials 38 | String base64Credentials = authorization.substring("Basic".length()).trim(); 39 | byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); 40 | String credentials = new String(credDecoded, StandardCharsets.UTF_8); 41 | // credentials = username:password 42 | final String[] values = credentials.split(":", 2); 43 | String peerId = values[0]; 44 | String accessKey = values[1]; 45 | boolean peerValid = this.signalingServ.validatePeer(Long.valueOf(peerId)); 46 | boolean channelValid = this.signalingServ.validateChannel(channelId, accessKey); 47 | if (peerValid && channelValid) { 48 | this.peerDetails.setChannelId(channelId); 49 | this.peerDetails.setPeerId(Long.valueOf(peerId)); 50 | } else { 51 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 52 | } 53 | } 54 | Object result = joinPoint.proceed(); 55 | return result; 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/Base58.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Arrays; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Base58 is a way to encode Bitcoin addresses (or arbitrary data) as 10 | * alphanumeric strings. 11 | *

12 | * Note that this is not the same base58 as used by Flickr, which you may find 13 | * referenced around the Internet. 14 | *

15 | * Satoshi explains: why base-58 instead of standard base-64 encoding? 16 | *

    17 | *
  • Don't want 0OIl characters that look the same in some fonts and could be 18 | * used to create visually identical looking account numbers.
  • 19 | *
  • A string with non-alphanumeric characters is not as easily accepted as an 20 | * account number.
  • 21 | *
  • E-mail usually won't line-break if there's no punctuation to break 22 | * at.
  • 23 | *
  • Doubleclicking selects the whole number as one word if it's all 24 | * alphanumeric.
  • 25 | *
26 | *

27 | * However, note that the encoding/decoding runs in O(n²) time, so it is 28 | * not useful for large data. 29 | *

30 | * The basic idea of the encoding is to treat the data bytes as a large number 31 | * represented using base-256 digits, convert the number to be represented using 32 | * base-58 digits, preserve the exact number of leading zeros (which are 33 | * otherwise lost during the mathematical operations on the numbers), and 34 | * finally represent the resulting base-58 digits as alphanumeric ASCII 35 | * characters. 36 | */ 37 | @Component 38 | public class Base58 { 39 | public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); 40 | private static final char ENCODED_ZERO = ALPHABET[0]; 41 | private static final int[] INDEXES = new int[128]; 42 | static { 43 | Arrays.fill(INDEXES, -1); 44 | for (int i = 0; i < ALPHABET.length; i++) { 45 | INDEXES[ALPHABET[i]] = i; 46 | } 47 | } 48 | 49 | /** 50 | * Encodes the given bytes as a base58 string (no checksum is appended). 51 | * 52 | * @param input the bytes to encode 53 | * @return the base58-encoded string 54 | */ 55 | public static String encode(byte[] input) { 56 | if (input.length == 0) { 57 | return ""; 58 | } 59 | // Count leading zeros. 60 | int zeros = 0; 61 | while (zeros < input.length && input[zeros] == 0) { 62 | ++zeros; 63 | } 64 | // Convert base-256 digits to base-58 digits (plus conversion to ASCII 65 | // characters) 66 | input = Arrays.copyOf(input, input.length); // since we modify it in-place 67 | char[] encoded = new char[input.length * 2]; // upper bound 68 | int outputStart = encoded.length; 69 | for (int inputStart = zeros; inputStart < input.length;) { 70 | encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)]; 71 | if (input[inputStart] == 0) { 72 | ++inputStart; // optimization - skip leading zeros 73 | } 74 | } 75 | // Preserve exactly as many leading encoded zeros in output as there were 76 | // leading zeros in input. 77 | while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { 78 | ++outputStart; 79 | } 80 | while (--zeros >= 0) { 81 | encoded[--outputStart] = ENCODED_ZERO; 82 | } 83 | // Return encoded string (including encoded leading zeros). 84 | return new String(encoded, outputStart, encoded.length - outputStart); 85 | } 86 | 87 | /** 88 | * Decodes the given base58 string into the original data bytes. 89 | * 90 | * @param input the base58-encoded string to decode 91 | * @return the decoded data bytes 92 | */ 93 | public static byte[] decode(String input) { 94 | if (input.length() == 0) { 95 | return new byte[0]; 96 | } 97 | // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 98 | // digits). 99 | byte[] input58 = new byte[input.length()]; 100 | for (int i = 0; i < input.length(); ++i) { 101 | char c = input.charAt(i); 102 | int digit = c < 128 ? INDEXES[c] : -1; 103 | if (digit < 0) { 104 | throw new IllegalStateException("InvalidCharacter in base 58"); 105 | } 106 | input58[i] = (byte) digit; 107 | } 108 | // Count leading zeros. 109 | int zeros = 0; 110 | while (zeros < input58.length && input58[zeros] == 0) { 111 | ++zeros; 112 | } 113 | // Convert base-58 digits to base-256 digits. 114 | byte[] decoded = new byte[input.length()]; 115 | int outputStart = decoded.length; 116 | for (int inputStart = zeros; inputStart < input58.length;) { 117 | decoded[--outputStart] = divmod(input58, inputStart, 58, 256); 118 | if (input58[inputStart] == 0) { 119 | ++inputStart; // optimization - skip leading zeros 120 | } 121 | } 122 | // Ignore extra leading zeroes that were added during the calculation. 123 | while (outputStart < decoded.length && decoded[outputStart] == 0) { 124 | ++outputStart; 125 | } 126 | // Return decoded data (including original number of leading zeros). 127 | return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); 128 | } 129 | 130 | public static BigInteger decodeToBigInteger(String input) { 131 | return new BigInteger(1, decode(input)); 132 | } 133 | 134 | /** 135 | * Divides a number, represented as an array of bytes each containing a single 136 | * digit in the specified base, by the given divisor. The given number is 137 | * modified in-place to contain the quotient, and the return value is the 138 | * remainder. 139 | * 140 | * @param number the number to divide 141 | * @param firstDigit the index within the array of the first non-zero digit 142 | * (this is used for optimization by skipping the leading 143 | * zeros) 144 | * @param base the base in which the number's digits are represented (up 145 | * to 256) 146 | * @param divisor the number to divide by (up to 256) 147 | * @return the remainder of the division operation 148 | */ 149 | private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { 150 | // this is just long division which accounts for the base of the input digits 151 | int remainder = 0; 152 | for (int i = firstDigit; i < number.length; i++) { 153 | int digit = (int) number[i] & 0xFF; 154 | int temp = remainder * base + digit; 155 | number[i] = (byte) (temp / divisor); 156 | remainder = temp % divisor; 157 | } 158 | return (byte) remainder; 159 | } 160 | } -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/Constant.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | public final class Constant { 4 | static final long ORIGIN_FILE_PART = 0L; 5 | } 6 | -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/FileIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Connection; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | 9 | import org.hibernate.HibernateException; 10 | import org.hibernate.engine.spi.SharedSessionContractImplementor; 11 | import org.hibernate.id.IdentifierGenerator; 12 | 13 | import com.f2f.backend.ds.FilePart; 14 | 15 | public class FileIdGenerator implements IdentifierGenerator { 16 | 17 | @Override 18 | public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { 19 | Connection connection = session.connection(); 20 | FilePart file = (FilePart) object; 21 | final long originFileId = file.getOrigin(); 22 | final int index = file.getIndex(); 23 | try { 24 | Statement statement = connection.createStatement(); 25 | if (index == 0 && originFileId == Constant.ORIGIN_FILE_PART) { 26 | ResultSet rs = statement.executeQuery("SELECT nextval('file_id_seq')"); 27 | if (rs.next()) { 28 | Long id = rs.getLong(1); 29 | return id; 30 | } 31 | } else if (originFileId == Constant.ORIGIN_FILE_PART) { 32 | ResultSet rs = statement.executeQuery("SELECT currval('file_id_seq')"); 33 | if (rs.next()) { 34 | Long id = rs.getLong(1); 35 | return id; 36 | } 37 | } else { 38 | return originFileId; 39 | } 40 | 41 | } catch (SQLException e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | return null; 46 | } 47 | } -------------------------------------------------------------------------------- /f2f-backend/src/main/java/com/f2f/backend/utilities/PeerDetails.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend.utilities; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Component 9 | @Getter 10 | @Setter 11 | public class PeerDetails { 12 | Long peerId; 13 | Long channelId; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /f2f-backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://192.168.1.50:5432/postgres 2 | spring.datasource.username=postgres 3 | spring.datasource.password=postgres 4 | spring.datasource.driverClassName=org.postgresql.Driver 5 | spring.jpa.generate-ddl=true 6 | spring.jpa.hibernate.ddl-auto=update 7 | server.port=5231 -------------------------------------------------------------------------------- /f2f-backend/src/test/java/com/f2f/backend/BackendApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.f2f.backend; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BackendApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /f2f-frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /f2f-frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /f2f-frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /f2f-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp 5 | /out-tsc 6 | /dist 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /f2f-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | COPY nginx.conf /etc/nginx/nginx.conf 3 | # COPY nginx.conf /etc/nginx/conf.d/default.conf 4 | RUN mkdir /etc/nginx/sites-enabled 5 | COPY f2f.nhvu95.com.conf /etc/nginx/sites-available/f2f.nhvu95.com 6 | #COPY E:/Angular/webrtc/f2f-frontend/dist/webrtc /usr/share/nginx/html/f2f.nhvu95.com 7 | RUN ln -s /etc/nginx/sites-available/f2f.nhvu95.com /etc/nginx/sites-enabled/ -------------------------------------------------------------------------------- /f2f-frontend/README.md: -------------------------------------------------------------------------------- 1 | # Webrtc 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.5. 4 | 5 | ## Sample 6 | 7 | Sample here: [File 2 Friends](https://f2f.nhvu95.com/) 8 | 9 | ## Development server 10 | 11 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 12 | 13 | ## Code scaffolding 14 | 15 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 16 | 17 | ## Build 18 | 19 | Run `npm run build` to build the project. The build artifacts will be stored in the `dist/webrtc` directory. 20 | 21 | ## Docker host 22 | After run build, to host in docker 23 | 24 | `docker compose up -d` 25 | 26 | ## Running unit tests 27 | 28 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 29 | 30 | ## Running end-to-end tests 31 | 32 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 33 | 34 | ## Further help 35 | 36 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 37 | -------------------------------------------------------------------------------- /f2f-frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "webrtc": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:class": { 10 | "skipTests": true 11 | }, 12 | "@schematics/angular:component": { 13 | "inlineTemplate": true, 14 | "inlineStyle": true, 15 | "style": "scss", 16 | "skipTests": true 17 | }, 18 | "@schematics/angular:directive": { 19 | "skipTests": true 20 | }, 21 | "@schematics/angular:guard": { 22 | "skipTests": true 23 | }, 24 | "@schematics/angular:interceptor": { 25 | "skipTests": true 26 | }, 27 | "@schematics/angular:module": { 28 | "skipTests": true 29 | }, 30 | "@schematics/angular:pipe": { 31 | "skipTests": true 32 | }, 33 | "@schematics/angular:service": { 34 | "skipTests": true 35 | } 36 | }, 37 | "root": "", 38 | "sourceRoot": "src", 39 | "prefix": "et", 40 | "architect": { 41 | "build": { 42 | "builder": "@angular-builders/custom-webpack:browser", 43 | "options": { 44 | "customWebpackConfig": { 45 | "path": "./webpack.config.ts", 46 | "mergeRules": { 47 | "externals": "replace" 48 | } 49 | }, 50 | "outputPath": "dist/webrtc", 51 | "index": "src/index.html", 52 | "main": "src/main.ts", 53 | "polyfills": "src/polyfills.ts", 54 | "tsConfig": "tsconfig.app.json", 55 | "stylePreprocessorOptions": { 56 | "includePaths": [ 57 | "src/assets" 58 | ] 59 | }, 60 | "assets": [ 61 | "src/favicon.ico", 62 | "src/assets", 63 | { 64 | "glob": "**/*", 65 | "input": "node_modules/@taiga-ui/icons/src", 66 | "output": "assets/taiga-ui/icons" 67 | } 68 | ], 69 | "styles": [ 70 | "src/styles.scss", 71 | "node_modules/@taiga-ui/core/styles/taiga-ui-global.less", 72 | "node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", 73 | "node_modules/@taiga-ui/core/styles/theme/variables.less", 74 | "node_modules/@taiga-ui/core/styles/theme/wrapper.less" 75 | ], 76 | "scripts": [], 77 | "vendorChunk": true, 78 | "extractLicenses": false, 79 | "buildOptimizer": false, 80 | "sourceMap": true, 81 | "optimization": false, 82 | "namedChunks": true 83 | }, 84 | "configurations": { 85 | "production": { 86 | "fileReplacements": [ 87 | { 88 | "replace": "src/environments/environment.ts", 89 | "with": "src/environments/environment.prod.ts" 90 | } 91 | ], 92 | "optimization": true, 93 | "outputHashing": "all", 94 | "sourceMap": false, 95 | "namedChunks": false, 96 | "extractLicenses": true, 97 | "vendorChunk": false, 98 | "buildOptimizer": false, 99 | "budgets": [ 100 | { 101 | "type": "initial", 102 | "maximumWarning": "2mb", 103 | "maximumError": "5mb" 104 | }, 105 | { 106 | "type": "anyComponentStyle", 107 | "maximumWarning": "6kb", 108 | "maximumError": "10kb" 109 | } 110 | ] 111 | } 112 | } 113 | }, 114 | "serve": { 115 | "builder": "@angular-builders/custom-webpack:dev-server", 116 | "options": { 117 | "browserTarget": "webrtc:build" 118 | }, 119 | "configurations": { 120 | "production": { 121 | "browserTarget": "webrtc:build:production" 122 | } 123 | } 124 | }, 125 | "extract-i18n": { 126 | "builder": "@angular-devkit/build-angular:extract-i18n", 127 | "options": { 128 | "browserTarget": "webrtc:build" 129 | } 130 | }, 131 | "test": { 132 | "builder": "@angular-devkit/build-angular:karma", 133 | "options": { 134 | "main": "src/test.ts", 135 | "polyfills": "src/polyfills.ts", 136 | "tsConfig": "tsconfig.spec.json", 137 | "karmaConfig": "karma.conf.js", 138 | "assets": [ 139 | "src/favicon.ico", 140 | "src/assets", 141 | { 142 | "glob": "**/*", 143 | "input": "node_modules/@taiga-ui/icons/src", 144 | "output": "assets/taiga-ui/icons" 145 | } 146 | ], 147 | "styles": [ 148 | "src/styles.scss", 149 | "node_modules/@taiga-ui/core/styles/taiga-ui-global.less", 150 | "node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", 151 | "node_modules/@taiga-ui/core/styles/theme/variables.less", 152 | "node_modules/@taiga-ui/core/styles/theme/wrapper.less" 153 | ], 154 | "scripts": [] 155 | } 156 | }, 157 | "lint": { 158 | "builder": "@angular-devkit/build-angular:tslint", 159 | "options": { 160 | "tsConfig": [ 161 | "tsconfig.app.json", 162 | "tsconfig.spec.json", 163 | "e2e/tsconfig.json" 164 | ], 165 | "exclude": [ 166 | "**/node_modules/**" 167 | ] 168 | } 169 | }, 170 | "e2e": { 171 | "builder": "@angular-devkit/build-angular:protractor", 172 | "options": { 173 | "protractorConfig": "e2e/protractor.conf.js", 174 | "devServerTarget": "webrtc:serve" 175 | }, 176 | "configurations": { 177 | "production": { 178 | "devServerTarget": "webrtc:serve:production" 179 | } 180 | } 181 | }, 182 | "deploy": { 183 | "builder": "angular-cli-ghpages:deploy", 184 | "options": {} 185 | } 186 | } 187 | } 188 | }, 189 | "defaultProject": "webrtc", 190 | "cli": { 191 | "analytics": "455533e8-f542-4e03-bab4-f80f9d9ed4fc" 192 | } 193 | } -------------------------------------------------------------------------------- /f2f-frontend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.10.2" 2 | services: 3 | web-server: 4 | build: . 5 | ports: 6 | - "80:80" 7 | - "443:443" 8 | container_name: f2f-frontend-container 9 | image: "f2f-frontend-image" 10 | restart: unless-stopped 11 | volumes: 12 | - './dist/webrtc:/usr/share/nginx/html/f2f.nhvu95.com' 13 | - '../../certbot/nhvu95.com:/etc/nginx/cert' 14 | -------------------------------------------------------------------------------- /f2f-frontend/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /f2f-frontend/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('webrtc app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /f2f-frontend/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('et-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /f2f-frontend/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /f2f-frontend/f2f.nhvu95.com.conf: -------------------------------------------------------------------------------- 1 | ## For f2f.nhvu95.com subdomain 2 | server { 3 | listen 80; 4 | listen [::]:80; 5 | 6 | listen 443 ssl; 7 | listen [::]:443 ssl; 8 | ssl_certificate cert/fullchain.pem; 9 | ssl_certificate_key cert/privkey.pem; 10 | 11 | root /usr/share/nginx/html/f2f.nhvu95.com; 12 | index index.html; 13 | server_name f2f.nhvu95.com www.f2f.nhvu95.com; 14 | location / { 15 | try_files $uri $uri/ /index.html; 16 | } 17 | } -------------------------------------------------------------------------------- /f2f-frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/webrtc'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /f2f-frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | events{} 2 | http { 3 | include /etc/nginx/mime.types; 4 | include /etc/nginx/sites-enabled/*; 5 | } -------------------------------------------------------------------------------- /f2f-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build --configuration=production && echo f2f.nhvu95.com > ./dist/webrtc/CNAME", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "host": "npm run build && echo f2f.nhvu95.com > ./dist/webrtc/CNAME && npm run docker-build && npm run docker-run" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular-builders/custom-webpack": "^11.1.1", 16 | "@angular/animations": "~12.0.0", 17 | "@angular/common": "~12.0.0", 18 | "@angular/compiler": "~12.0.0", 19 | "@angular/core": "~12.0.0", 20 | "@angular/forms": "~12.0.0", 21 | "@angular/localize": "~12.0.0", 22 | "@angular/platform-browser": "~12.0.0", 23 | "@angular/platform-browser-dynamic": "~12.0.0", 24 | "@angular/router": "~12.0.0", 25 | "@ngxs/store": "^3.7.1", 26 | "@stomp/ng2-stompjs": "^8.0.0", 27 | "@stomp/stompjs": "^6.1.0", 28 | "@taiga-ui/cdk": "^2.9.1", 29 | "@taiga-ui/core": "^2.9.1", 30 | "@taiga-ui/icons": "^2.9.1", 31 | "@taiga-ui/kit": "^2.9.1", 32 | "@types/lodash": "^4.14.189", 33 | "@w11k/ngx-componentdestroyed": "^5.0.2", 34 | "angular-progress-bar": "^1.0.11", 35 | "file-saver": "^2.0.5", 36 | "gh-pages": "^3.2.3", 37 | "immer": "^9.0.2", 38 | "ngx-clipboard": "^14.0.1", 39 | "ngx-file-drop": "^11.1.0", 40 | "ngxs-reset-plugin": "^1.4.0", 41 | "postcss-scss": "^3.0.5", 42 | "rxjs": "~6.5.5", 43 | "sass": "^1.32.13", 44 | "sass-loader": "^10.1.1", 45 | "streamsaver": "^2.0.6", 46 | "tslib": "^2.0.0", 47 | "uuid": "^8.3.2", 48 | "zone.js": "~0.11.4" 49 | }, 50 | "devDependencies": { 51 | "@angular-devkit/build-angular": "~12.0.0", 52 | "@angular/cli": "~12.0.0", 53 | "@angular/compiler-cli": "~12.0.0", 54 | "@types/jasmine": "~3.6.0", 55 | "@types/jasminewd2": "~2.0.3", 56 | "@types/node": "^12.11.1", 57 | "angular-cli-ghpages": "^1.0.0-rc.2", 58 | "autoprefixer": "^10.2.5", 59 | "codelyzer": "^6.0.0", 60 | "jasmine-core": "~3.6.0", 61 | "jasmine-spec-reporter": "~5.0.0", 62 | "karma": "~6.3.2", 63 | "karma-chrome-launcher": "~3.1.0", 64 | "karma-coverage-istanbul-reporter": "~3.0.2", 65 | "karma-jasmine": "~4.0.0", 66 | "karma-jasmine-html-reporter": "^1.5.0", 67 | "postcss": "^8.2.15", 68 | "postcss-loader": "^4.3.0", 69 | "protractor": "~7.0.0", 70 | "tailwindcss": "^2.1.2", 71 | "ts-node": "~8.3.0", 72 | "tslint": "~6.1.0", 73 | "typescript": "~4.2.4", 74 | "webpack-bundle-analyzer": "^4.5.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /f2f-frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | // postcss.config.js 2 | module.exports = { 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | } 7 | } -------------------------------------------------------------------------------- /f2f-frontend/scully/plugins/plugin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.myPlugin = void 0; 4 | const scully_1 = require("@scullyio/scully"); 5 | exports.myPlugin = 'myPlugin'; 6 | const myFunctionPlugin = async (html) => { 7 | return html; 8 | }; 9 | const validator = async () => []; 10 | scully_1.registerPlugin('postProcessByHtml', exports.myPlugin, myFunctionPlugin, validator); 11 | //# sourceMappingURL=plugin.js.map -------------------------------------------------------------------------------- /f2f-frontend/scully/plugins/plugin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"plugin.js","sourceRoot":"","sources":["plugin.ts"],"names":[],"mappings":";;;AACA,6CAAmE;AAEtD,QAAA,QAAQ,GAAG,UAAU,CAAC;AAEnC,MAAM,gBAAgB,GAAG,KAAK,EAAE,IAAY,EAAmB,EAAE;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;AAEjC,uBAAc,CAAC,mBAAmB,EAAE,gBAAQ,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC"} -------------------------------------------------------------------------------- /f2f-frontend/scully/plugins/plugin.ts: -------------------------------------------------------------------------------- 1 | 2 | import { registerPlugin } from '@scullyio/scully'; 3 | 4 | export const myPlugin = 'myPlugin'; 5 | 6 | const myFunctionPlugin = async (html: string): Promise => { 7 | return html; 8 | }; 9 | 10 | const validator = async () => []; 11 | 12 | registerPlugin('postProcessByHtml', myPlugin, myFunctionPlugin, validator); 13 | -------------------------------------------------------------------------------- /f2f-frontend/scully/tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "esModuleInterop": true, 6 | "importHelpers": false, 7 | "lib": ["ES2019", "dom"], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "target": "es2018", 12 | "types": ["node"], 13 | "skipLibCheck": true, 14 | "skipDefaultLibCheck": true, 15 | "typeRoots": ["../node_modules/@types"], 16 | "allowSyntheticDefaultImports": true 17 | }, 18 | "exclude": ["./**/*spec.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | pathMatch: 'full', 8 | loadChildren: () => 9 | import('./sender/sender.module').then((m) => m.SenderModule), 10 | }, 11 | { 12 | path: 'receiver', 13 | loadChildren: () => 14 | import('./receiver/receiver.module').then((m) => m.ReceiverModule), 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [RouterModule.forRoot(routes, {useHash: true})], 20 | exports: [RouterModule], 21 | }) 22 | export class AppRoutingModule {} 23 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |

3 |
4 |
5 | 6 | 10 | 14 | 15 |
16 | 17 |
18 | copyright: www.nhvu95.com 19 |
20 |
21 |

F2F

22 |
23 |
    24 |
  • 1. NO STORE YOUR FILE ON SERVER
  • 25 |
  • 2. SEND DIRECTLY TO YOUR FRIENDS
  • 26 |
  • 3. SECURED YOUR FILE
  • 27 |
  • 4. FREE
  • 28 |
29 |
30 |
31 | 38 | 45 |
46 | 47 |
48 | 55 | 62 |
63 |
64 |
68 |
69 |
70 |
71 |
72 | 73 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | import { NavigationEnd, Router } from '@angular/router'; 3 | import { Select, Store } from '@ngxs/store'; 4 | import { ClipboardService } from 'ngx-clipboard'; 5 | import { Observable, of } from 'rxjs'; 6 | import { filter, map } from 'rxjs/operators'; 7 | import { AppSelectors } from '@shared/app.selector'; 8 | import { SharedAppService } from '@shared/shared-app.service'; 9 | 10 | @Component({ 11 | selector: 'et-root', 12 | templateUrl: './app.component.html', 13 | styleUrls: ['./app.component.scss'], 14 | encapsulation: ViewEncapsulation.None, 15 | }) 16 | export class AppComponent { 17 | title = 'webrtc'; 18 | @Select(AppSelectors.isReadyToSend) readyToSend$: Observable; 19 | isSender$: Observable = of(false); 20 | notClickCopy = true; 21 | 22 | activeItemIndex = 0; 23 | 24 | constructor( 25 | private router: Router, 26 | private store: Store, 27 | private ngxCopy: ClipboardService, 28 | private commonService: SharedAppService 29 | ) { 30 | this.isSender$ = this.router.events.pipe( 31 | filter((e) => e instanceof NavigationEnd), 32 | map((e: NavigationEnd) => e.url === '/' || e.url === 'sender') 33 | ); 34 | 35 | this.router.routeReuseStrategy.shouldReuseRoute = () => { 36 | return false; 37 | }; 38 | 39 | this.router.events.subscribe((evt) => { 40 | if (evt instanceof NavigationEnd) { 41 | // trick the Router into believing it's last link wasn't previously loaded 42 | this.router.navigated = false; 43 | // if you need to scroll back to top, here is the right place 44 | window.scrollTo(0, 0); 45 | } 46 | }); 47 | } 48 | 49 | /** 50 | * Copy data from state to buffer 51 | */ 52 | copyData(): void { 53 | this.notClickCopy = false; 54 | const channelId = this.store.selectSnapshot( 55 | AppSelectors.getChannelId 56 | ); 57 | const accessKey = this.store.selectSnapshot( 58 | AppSelectors.getAccessKey 59 | ); 60 | this.ngxCopy.copy(channelId + '\n' + accessKey); 61 | } 62 | 63 | /** 64 | * Copy Link 65 | */ 66 | copyLink(): void { 67 | this.notClickCopy = false; 68 | this.commonService.showNotify( 69 | 'Copy link success\nDon\'t close this tab until your friend downloads complete', 70 | 'Attention' 71 | ); 72 | const channelId = this.store.selectSnapshot( 73 | AppSelectors.getChannelId 74 | ); 75 | const accessKey = this.store.selectSnapshot( 76 | AppSelectors.getAccessKey 77 | ); 78 | 79 | const href = window.location.href; 80 | const idx = href.lastIndexOf('/'); 81 | const url = href.substring(0, idx); 82 | const finalContent = `${url}/receiver?channelId=${channelId}&accessKey=${accessKey}`; 83 | this.ngxCopy.copy(finalContent); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { AsyncPipe } from '@angular/common'; 2 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 3 | import { InjectionToken, NgModule } from '@angular/core'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | import { NgxsModule } from '@ngxs/store'; 7 | import { 8 | InjectableRxStompConfig, 9 | RxStompService, 10 | rxStompServiceFactory, 11 | } from '@stomp/ng2-stompjs'; 12 | import { 13 | TuiButtonModule, 14 | TuiDialogModule, 15 | TuiNotificationsModule, 16 | TuiRootModule, 17 | TuiSvgModule, 18 | } from '@taiga-ui/core'; 19 | import { TuiTabsModule } from '@taiga-ui/kit'; 20 | import { environment } from 'src/environments/environment'; 21 | import { AppRoutingModule } from './app-routing.module'; 22 | import { AppComponent } from './app.component'; 23 | import { AppState } from '@shared/app.state'; 24 | import { HttpConfigInterceptor } from './interceptor/http.interceptor'; 25 | import { SharedAppService } from '@shared/shared-app.service'; 26 | import { RTCRxStompConfig } from '@services/rx-stomp.config'; 27 | 28 | @NgModule({ 29 | declarations: [AppComponent], 30 | imports: [ 31 | BrowserModule, 32 | BrowserAnimationsModule, 33 | AppRoutingModule, 34 | TuiRootModule, 35 | // TuiAvatarModule, 36 | TuiButtonModule, 37 | TuiDialogModule, 38 | TuiNotificationsModule, 39 | NgxsModule.forRoot([AppState], { 40 | developmentMode: !environment.production, 41 | }), 42 | HttpClientModule, 43 | TuiTabsModule, 44 | TuiSvgModule, 45 | ], 46 | providers: [ 47 | AsyncPipe, 48 | { 49 | provide: HTTP_INTERCEPTORS, 50 | useClass: HttpConfigInterceptor, 51 | multi: true, 52 | }, 53 | { 54 | provide: InjectableRxStompConfig, 55 | useValue: RTCRxStompConfig, 56 | }, 57 | { 58 | provide: RxStompService, 59 | useFactory: rxStompServiceFactory, 60 | deps: [InjectableRxStompConfig], 61 | }, 62 | SharedAppService 63 | ], 64 | bootstrap: [AppComponent], 65 | exports: [], 66 | }) 67 | export class AppModule { 68 | constructor() {} 69 | } 70 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/interceptor/http.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpErrorResponse, 3 | HttpEvent, 4 | HttpHandler, 5 | HttpInterceptor, 6 | HttpRequest, 7 | HttpResponse, 8 | } from '@angular/common/http'; 9 | import { Injectable } from '@angular/core'; 10 | import { Store } from '@ngxs/store'; 11 | import { Observable, of } from 'rxjs'; 12 | import { catchError, map } from 'rxjs/operators'; 13 | import { AppSelectors } from '@shared/app.selector'; 14 | import { SharedAppService } from '@shared/shared-app.service'; 15 | 16 | @Injectable() 17 | export class HttpConfigInterceptor implements HttpInterceptor { 18 | constructor(private commonService: SharedAppService, private store: Store) {} 19 | intercept( 20 | request: HttpRequest, 21 | next: HttpHandler 22 | ): Observable> { 23 | // ... 24 | let accessKey = this.store.selectSnapshot(AppSelectors.getAccessKey); 25 | let peerId = this.store.selectSnapshot(AppSelectors.getPeerId); 26 | 27 | if (accessKey) { 28 | request = request.clone({ 29 | headers: request.headers.set( 30 | 'Authorization', 31 | `Basic ${btoa(peerId + ':' + accessKey)}` 32 | ), 33 | }); 34 | } 35 | 36 | if (!request.headers.has('Content-Type')) { 37 | request = request.clone({ 38 | headers: request.headers.set('Content-Type', 'application/json'), 39 | }); 40 | } 41 | if (!request.headers.has('Access-Control-Allow-Origin')) { 42 | request = request.clone({ 43 | headers: request.headers.set('Access-Control-Allow-Origin', '*'), 44 | }); 45 | } 46 | 47 | request = request.clone({ 48 | headers: request.headers.set('Accept', 'application/json'), 49 | }); 50 | 51 | return next.handle(request).pipe( 52 | map((event: HttpEvent) => { 53 | return event; 54 | }), 55 | catchError((error: HttpErrorResponse) => { 56 | if (error.status === 404 || error.status === 400) { 57 | this.commonService.showDialog(error.error?.message).subscribe(); 58 | } 59 | return of(null); 60 | }) 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.action.ts: -------------------------------------------------------------------------------- 1 | import { EPeerState, IFilePartInformation } from '@shared/app.model'; 2 | 3 | export class AddNewFileInfoAction { 4 | static readonly type = '[RECEIVER] AddNewFileInfoAction'; 5 | constructor(public file: IFilePartInformation) {} 6 | } 7 | 8 | export class SetCurrentStepAction { 9 | static readonly type = '[RECEIVER] SetCurrentStepAction'; 10 | constructor(public step: number) {} 11 | } 12 | 13 | export class GetChannelOwnerAndListFileAction { 14 | static readonly type = '[RECEIVER] GetChannelOwnerAndListFileAction'; 15 | constructor() {} 16 | } 17 | 18 | export class StartLeechingAction { 19 | static readonly type = '[RECEIVER] StartLeechingAction'; 20 | constructor() {} 21 | } 22 | 23 | export class ReceiverResetStateToDefaultAction { 24 | static readonly type = '[RECEIVER] ReceiverResetStateToDefaultAction'; 25 | constructor() {} 26 | } 27 | 28 | export class UpdateFileReceiveProgressAction { 29 | static readonly type = '[RECEIVER] UpdateFileReceiveProgressAction'; 30 | constructor(public fileId: number, public increaseSize: number) {} 31 | } 32 | 33 | export class UpdateReceiverStatusAction { 34 | static readonly type = '[RECEIVER] UpdatePeerStatus'; 35 | constructor(public peerState: EPeerState, public fileId: number) {} 36 | } 37 | 38 | export class DownloadFilePartCompleteAction { 39 | static readonly type = '[RECEIVER] DownloadFilePartCompleteAction'; 40 | constructor(public fileId: number, public fileIndex: number) {} 41 | } 42 | 43 | export class CloseReceiverDataChannelAction { 44 | static readonly type = '[RECEIVER] CloseReceiverDataChannelAction'; 45 | constructor() {} 46 | } 47 | 48 | export class TryToGettingFile { 49 | static readonly type = '[RECEIVER] TryToGettingFile'; 50 | constructor() {} 51 | } -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 |
8 | If you like my project 9 |
10 | 18 |
19 | please 20 | YOUR FILE ARE SECURED 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | PLEASE ENTER ID AND KEY... 29 |
30 | 31 |
32 |
33 | 39 |
40 |
41 |
42 |
43 |
44 | {{ files[0].fileName }} 45 |
46 | 47 |
48 |
49 | 57 |
58 |
59 |
60 |
61 |
62 | {{ files[1].fileName }} 63 |
64 | 65 |
66 |
67 | 75 |
76 |
77 |
78 |
79 |
80 | {{ files[2].fileName }} 81 |
82 | 83 |
84 |
85 | 93 |
94 |
95 | ID: 96 |
97 | 98 | Ask your friend about shared key 99 | 100 | 101 | Ask your friend about shared id 102 | 103 |
104 | KEY: 105 | 106 | 111 | 112 | 123 | 133 | 143 | 144 | 145 | 146 | 147 | 148 |
149 |
150 |
151 |
152 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.component.scss: -------------------------------------------------------------------------------- 1 | // component css goes here 2 | $bg-image: url("/assets/images/icons_set.svg"); 3 | $bg-size: 625px 250px; 4 | 5 | ::ng-deep .ngx-file-drop__content { 6 | position: absolute; 7 | height: unset !important; 8 | } 9 | ::ng-deep .ngx-file-drop__drop-zone { 10 | border: unset !important; 11 | width: 356px; 12 | height: 358px !important; 13 | } 14 | 15 | ::ng-deep tui-stepper{ 16 | @apply relative; 17 | top: 480px; 18 | height: 60px; 19 | button * { 20 | margin-left: auto !important; 21 | margin-right: auto !important; 22 | } 23 | button:nth-child(1){ 24 | @apply mr-auto; 25 | @apply ml-10 26 | } 27 | button:nth-child(2){ 28 | @apply mx-auto; 29 | } 30 | button:nth-child(3){ 31 | @apply ml-auto; 32 | @apply mr-6 33 | } 34 | } 35 | 36 | // [data-tui-host-size='l'][_nghost-mba-c143] { 37 | // height: var(--tui-height-l); 38 | // min-height: var(--tui-height-l); 39 | // max-height: var(--tui-height-l); 40 | // font-size: 15px; 41 | // } 42 | 43 | .e2_22{ 44 | background-image: $bg-image; 45 | background-size: $bg-size; 46 | background-position-x: 0px; 47 | background-position-y: 0px; 48 | width: 312.5px; 49 | left: 21px; 50 | } 51 | 52 | .donate{ 53 | width: 100%; 54 | height: 100%; 55 | } -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | ChangeDetectionStrategy, 4 | ChangeDetectorRef, 5 | Component, 6 | OnDestroy, 7 | ViewChild 8 | } from '@angular/core'; 9 | import { FormControl, FormGroup } from '@angular/forms'; 10 | import { ActivatedRoute } from '@angular/router'; 11 | import { Select, Store } from '@ngxs/store'; 12 | import { AccessChannelAction, SetScreenAction } from '@shared/app.action'; 13 | import { IFilePartSending } from '@shared/app.model'; 14 | import { AppSelectors } from '@shared/app.selector'; 15 | import { SharedAppService } from '@shared/shared-app.service'; 16 | import { TuiStepperComponent } from '@taiga-ui/kit'; 17 | import { 18 | OnDestroyMixin, 19 | untilComponentDestroyed 20 | } from '@w11k/ngx-componentdestroyed'; 21 | import { NgxFileDropEntry } from 'ngx-file-drop'; 22 | import { Observable } from 'rxjs'; 23 | import { concatMap, debounceTime, tap } from 'rxjs/operators'; 24 | import { 25 | CloseReceiverDataChannelAction, 26 | GetChannelOwnerAndListFileAction, 27 | ReceiverResetStateToDefaultAction, 28 | SetCurrentStepAction, 29 | StartLeechingAction 30 | } from './receiver.action'; 31 | import { ReceiverSelectors } from './receiver.selectors'; 32 | 33 | @Component({ 34 | selector: 'et-receiver', 35 | templateUrl: 'receiver.component.html', 36 | styleUrls: ['receiver.component.scss'], 37 | changeDetection: ChangeDetectionStrategy.OnPush, 38 | }) 39 | export class ReceiverComponent 40 | extends OnDestroyMixin 41 | implements AfterViewInit, OnDestroy 42 | { 43 | public files: NgxFileDropEntry[] = []; 44 | showGuide = false; 45 | 46 | @Select(AppSelectors.getChannelId) channelId$: Observable; 47 | @Select(AppSelectors.getAccessKey) accessKey$: Observable; 48 | @Select(ReceiverSelectors.localFiles) files$: Observable; 49 | @Select(ReceiverSelectors.steps) steps$: Observable; 50 | @Select(ReceiverSelectors.currentStep) currentStep$: Observable; 51 | 52 | leechForm: FormGroup; 53 | @ViewChild('stepper') stepper: TuiStepperComponent; 54 | 55 | constructor( 56 | private store: Store, 57 | private commonService: SharedAppService, 58 | private activeRoute: ActivatedRoute, 59 | private cdr: ChangeDetectorRef 60 | ) { 61 | super(); 62 | this.store.dispatch(new ReceiverResetStateToDefaultAction()); 63 | this.store.dispatch(new AccessChannelAction(null, null)); 64 | 65 | this.leechForm = new FormGroup({ 66 | channelId: new FormControl(''), 67 | accessKey: new FormControl(''), 68 | }); 69 | 70 | this.leechForm.valueChanges.pipe().subscribe((res) => { 71 | if (res.channelId && res.channelId.includes(' ')) { 72 | const val = res.channelId.split(' '); 73 | this.leechForm.setValue({ channelId: val[0], accessKey: val[1] }); 74 | } else if (res.accessKey && res.accessKey.includes(' ')) { 75 | const val = res.accessKey.split(' '); 76 | this.leechForm.setValue({ channelId: val[0], accessKey: val[1] }); 77 | } 78 | if (res.channelId && res.accessKey) { 79 | this.showGuide = true; 80 | this.cdr.detectChanges(); 81 | this.store.dispatch( 82 | new AccessChannelAction(res.channelId, res.accessKey) 83 | ); 84 | } 85 | }); 86 | } 87 | 88 | /** 89 | * On After view Init 90 | */ 91 | ngAfterViewInit(): void { 92 | this.store.dispatch(new SetScreenAction('receiver')); 93 | 94 | const queryParams = this.activeRoute.snapshot.queryParams; 95 | if (Object.keys(queryParams).length > 0) { 96 | this.leechForm.patchValue(queryParams); 97 | } 98 | 99 | this.store 100 | .select(AppSelectors.isReadyToReceive) 101 | .pipe( 102 | untilComponentDestroyed(this), 103 | tap((res) => { 104 | if (res) { 105 | this.store.dispatch(new GetChannelOwnerAndListFileAction()); 106 | } 107 | }), 108 | debounceTime(1000) 109 | ) 110 | .subscribe((res) => { 111 | if (res) { 112 | this.startLeech(); 113 | } 114 | }); 115 | } 116 | 117 | /** 118 | * On Destroy 119 | */ 120 | ngOnDestroy(): void { 121 | this.store.dispatch(new CloseReceiverDataChannelAction()); 122 | super.ngOnDestroy(); 123 | } 124 | 125 | /** 126 | * Start getting file (leeching file) 127 | */ 128 | startLeech(): void { 129 | const self = this; 130 | const channelId: string = this.leechForm.get('channelId').value; 131 | const accessKey: string = this.leechForm.get('accessKey').value; 132 | if (channelId.trim() === '' || accessKey.trim() === '') { 133 | this.commonService.showDialog('Please enter id and key!!').subscribe(); 134 | self.stepper.activeItemIndex = -1; 135 | } else { 136 | this.leechForm.disable(); 137 | this.showGuide = false; 138 | this.cdr.detectChanges(); 139 | this.store.dispatch(new AccessChannelAction(channelId, accessKey)); 140 | this.store 141 | .dispatch(new SetCurrentStepAction(1)) 142 | .pipe( 143 | concatMap((val) => { 144 | return this.store.dispatch(new StartLeechingAction()); 145 | }) 146 | ) 147 | .subscribe(); 148 | } 149 | } 150 | 151 | onClose(item): void {} 152 | 153 | /** 154 | * Show donate dialog with QR code 155 | * @param content HTMLTemplate 156 | */ 157 | showDonate(content): void { 158 | this.commonService.showDialogWithTemplate(content); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.module.ts: -------------------------------------------------------------------------------- 1 | // Angular Imports 2 | import { AsyncPipe, CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterModule } from '@angular/router'; 6 | import { NgxsModule } from '@ngxs/store'; 7 | import { FileNamePipe } from '@shared/file-name.pipe'; 8 | import { SharedAppModule } from '@shared/shared-app.module'; 9 | import { TuiButtonModule, TuiTextfieldControllerModule } from '@taiga-ui/core'; 10 | import { 11 | TuiInputModule, 12 | TuiStepperComponent, 13 | TuiStepperModule 14 | } from '@taiga-ui/kit'; 15 | import { ProgressBarModule } from 'angular-progress-bar'; 16 | import { NgxFileDropModule } from 'ngx-file-drop'; 17 | // This Module's Components 18 | import { ReceiverComponent } from './receiver.component'; 19 | import { ReceiverState } from './receiver.state'; 20 | 21 | @NgModule({ 22 | imports: [ 23 | CommonModule, 24 | RouterModule.forChild([ 25 | { 26 | path: '', 27 | component: ReceiverComponent, 28 | }, 29 | ]), 30 | NgxsModule.forFeature([ReceiverState]), 31 | TuiButtonModule, 32 | TuiStepperModule, 33 | NgxFileDropModule, 34 | FormsModule, 35 | ReactiveFormsModule, 36 | TuiInputModule, 37 | ProgressBarModule, 38 | TuiTextfieldControllerModule, 39 | SharedAppModule, 40 | ], 41 | providers: [ 42 | AsyncPipe, 43 | TuiStepperComponent, 44 | FileNamePipe, 45 | ], 46 | declarations: [ReceiverComponent], 47 | exports: [ReceiverComponent], 48 | }) 49 | export class ReceiverModule {} 50 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.selectors.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | import { ReceiverState, ReceiverStateModel } from './receiver.state'; 3 | import {IFilePartSending, ISteps} from '@shared/app.model'; 4 | 5 | export class ReceiverSelectors { 6 | 7 | @Selector([ReceiverState]) 8 | static steps(state: ReceiverStateModel): ISteps[] { 9 | return state.steps; 10 | } 11 | 12 | @Selector([ReceiverState]) 13 | static currentStep(state: ReceiverStateModel): number { 14 | return state.currentStep; 15 | } 16 | 17 | @Selector([ReceiverState]) 18 | static localFiles(state: ReceiverStateModel): IFilePartSending[] { 19 | return state.localFiles; 20 | } 21 | 22 | @Selector([ReceiverState]) 23 | static localFilesComplete(state: ReceiverStateModel): IFilePartSending[] { 24 | return state.localFiles.filter(file => file.currentSize === file.fileSize); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/receiver/receiver.state.ts: -------------------------------------------------------------------------------- 1 | import { HttpErrorResponse } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Router } from '@angular/router'; 4 | import { Action, State, StateContext, Store } from '@ngxs/store'; 5 | import { SignalingReceiverService } from '@services/signaling-receiver.service'; 6 | import {EPeerState, _TInstanceState, ISteps, IFilePartSending} from '@shared/app.model'; 7 | import { AppSelectors } from '@shared/app.selector'; 8 | import { Debugger } from '@shared/debug.decorator'; 9 | import { SharedAppService } from '@shared/shared-app.service'; 10 | import produce from 'immer'; 11 | import { cloneDeep } from 'lodash'; 12 | import { takeUntil, tap } from 'rxjs/operators'; 13 | import { 14 | AddNewFileInfoAction, 15 | CloseReceiverDataChannelAction, 16 | DownloadFilePartCompleteAction, 17 | GetChannelOwnerAndListFileAction, 18 | ReceiverResetStateToDefaultAction, 19 | SetCurrentStepAction, 20 | StartLeechingAction, 21 | TryToGettingFile, 22 | UpdateFileReceiveProgressAction, 23 | UpdateReceiverStatusAction 24 | } from './receiver.action'; 25 | import { ReceiverSelectors } from './receiver.selectors'; 26 | 27 | const defaultState: ReceiverStateModel = { 28 | connectingPeerId: null, 29 | localFiles: [], 30 | peersCompleted: {}, 31 | steps: [ 32 | { state: 'normal', disable: false, name: 'Ready' }, 33 | { state: 'normal', disable: true, name: 'Connecting' }, 34 | { state: 'normal', disable: true, name: 'Leeching' }, 35 | ], 36 | currentStep: -1, 37 | }; 38 | 39 | // tslint:disable-next-line:class-name 40 | interface _ReceiverStateModel extends Partial<_TInstanceState> { 41 | steps: ISteps[]; 42 | currentStep: number; 43 | } 44 | 45 | export interface ReceiverStateModel extends Partial<_ReceiverStateModel> {} 46 | 47 | @State({ 48 | name: 'receiverState', 49 | defaults: cloneDeep(defaultState), 50 | }) 51 | @Injectable() 52 | export class ReceiverState { 53 | constructor( 54 | private signalingService: SignalingReceiverService, 55 | private commonService: SharedAppService, 56 | private router: Router, 57 | private store: Store 58 | ) {} 59 | 60 | /** 61 | * On state management initialize 62 | * @param ctx state context 63 | */ 64 | ngxsOnInit(ctx?: StateContext): void{} 65 | 66 | /** 67 | * Reset state to default 68 | * @param ctx state context 69 | * @param action object instance of ReceiverResetStateToDefaultAction 70 | */ 71 | @Debugger 72 | @Action(ReceiverResetStateToDefaultAction) 73 | resetToDefault( 74 | ctx: StateContext, 75 | action: ReceiverResetStateToDefaultAction 76 | ): void { 77 | ctx.setState(cloneDeep(defaultState)); 78 | } 79 | 80 | /** 81 | * @UI set current step 82 | * @param ctx state context 83 | * @param action object instance of SetCurrentStepAction 84 | */ 85 | @Debugger 86 | @Action(SetCurrentStepAction) 87 | setCurrentStep( 88 | ctx: StateContext, 89 | action: SetCurrentStepAction 90 | ): void { 91 | if (action.step === 3) { 92 | this.commonService 93 | .showNotifyAskUserConfirm('Download complete!') 94 | .pipe( 95 | tap((res) => { 96 | action.step = -1; 97 | }), 98 | takeUntil(this.router.events) 99 | ) 100 | .subscribe((res) => { 101 | this.router.navigateByUrl('/').then((_) => { 102 | window.location.reload(); 103 | }); 104 | }); 105 | } 106 | ctx.setState( 107 | produce((draft) => { 108 | draft.currentStep = action.step; 109 | draft.steps = draft.steps.map((step, idx) => { 110 | if (idx < action.step) { 111 | step.state = 'pass'; 112 | step.disable = true; 113 | } 114 | if (idx > action.step) { 115 | step.state = 'normal'; 116 | step.disable = true; 117 | } 118 | return step; 119 | }); 120 | }) 121 | ); 122 | // Get next peer 123 | } 124 | 125 | /** 126 | * Add files into list 127 | * @param ctx state context 128 | * @param action object instance of AddNewFileInfoAction 129 | */ 130 | @Debugger 131 | @Action(AddNewFileInfoAction) 132 | addFiles( 133 | ctx: StateContext, 134 | action: AddNewFileInfoAction 135 | ): void { 136 | ctx.setState( 137 | produce((draft) => { 138 | if ( 139 | draft.localFiles.findIndex((file) => { 140 | return file.fileId === action.file.fileId; 141 | }) === -1 142 | ) { 143 | draft.localFiles.push({ ...action.file }); 144 | } 145 | }) 146 | ); 147 | } 148 | 149 | /** 150 | * Get Channel Owner and get list of file 151 | * @param ctx state context 152 | */ 153 | @Debugger 154 | @Action(GetChannelOwnerAndListFileAction) 155 | getChannelOwnerAndListFile(ctx: StateContext): void { 156 | const channelId = this.store.selectSnapshot(AppSelectors.getChannelId); 157 | this.signalingService.getChannelOwner(channelId).subscribe(ownerId => { 158 | this.signalingService.sendGetListFilesMsg(ownerId); 159 | }); 160 | } 161 | 162 | /** 163 | * Start getting file (leeching file) 164 | * @param ctx state context 165 | */ 166 | @Debugger 167 | @Action(StartLeechingAction) 168 | startLeeching(ctx: StateContext): void { 169 | this.setCurrentStep(ctx, new SetCurrentStepAction(1)); 170 | ctx.dispatch(new TryToGettingFile()); 171 | } 172 | 173 | /** 174 | * Try to get a file 175 | * @param ctx state context 176 | */ 177 | @Debugger 178 | @Action(TryToGettingFile) 179 | tryToGettingFileRegistedInChannel(ctx: StateContext): void { 180 | const self = this; 181 | const channelId = this.store.selectSnapshot(AppSelectors.getChannelId); 182 | // Step 1 183 | this.signalingService.getNextPartInformation(channelId).subscribe( 184 | (res) => { 185 | if (res) { 186 | const senderId = res.ownerId; 187 | ctx.setState( 188 | produce((draft) => { 189 | draft.connectingPeerId = senderId; 190 | }) 191 | ); 192 | self.signalingService.setConnectingId(senderId); 193 | // Step 2 194 | self.signalingService.sendPreflightMsg(res.fileId, res.index); 195 | this.setCurrentStep(ctx, new SetCurrentStepAction(2)); 196 | } else { 197 | this.store.dispatch(new SetCurrentStepAction(3)); 198 | } 199 | }, 200 | (err: HttpErrorResponse) => { 201 | this.store.dispatch(new SetCurrentStepAction(3)); 202 | } 203 | ); 204 | } 205 | 206 | /** 207 | * Update @UI file progress 208 | * @param ctx state context 209 | * @param action instance of UpdateFileReceiveProgressAction 210 | */ 211 | @Debugger 212 | @Action(UpdateFileReceiveProgressAction) 213 | updateFileProgress( 214 | ctx: StateContext, 215 | action: UpdateFileReceiveProgressAction 216 | ): void { 217 | const state = ctx.getState(); 218 | ctx.setState( 219 | produce((draft) => { 220 | const idx = draft.localFiles.findIndex( 221 | (file) => file.fileId === action.fileId 222 | ); 223 | if (idx >= 0) { 224 | // tslint:disable-next-line:no-bitwise 225 | const currentSize: number = draft.localFiles[idx].currentSize | 0; 226 | draft.localFiles[idx].currentSize = currentSize + action.increaseSize; 227 | // prettier-ignore 228 | if ( draft.localFiles[idx].currentSize >= draft.localFiles[idx].fileSize ) { 229 | draft.localFiles[idx].currentSize = draft.localFiles[idx].fileSize; 230 | } 231 | } 232 | }) 233 | ); 234 | } 235 | 236 | /** 237 | * Update peer status to manages in server 238 | * @param ctx state context 239 | * @param action instance of UpdateReceiverStatusAction 240 | */ 241 | @Debugger 242 | @Action(UpdateReceiverStatusAction) 243 | updatePeerStatus( 244 | ctx: StateContext, 245 | action: UpdateReceiverStatusAction 246 | ): void { 247 | const peerId = this.store.selectSnapshot(AppSelectors.getPeerId); 248 | this.signalingService 249 | .updatePeerStatus(peerId, action.peerState, action.fileId) 250 | .subscribe(); 251 | 252 | const completedFiles = this.store.selectSnapshot( 253 | ReceiverSelectors.localFilesComplete 254 | ); 255 | if ( 256 | action.peerState === EPeerState.IDLE && 257 | completedFiles.length === ctx.getState().localFiles.length 258 | ) { 259 | } 260 | } 261 | 262 | /** 263 | * On download file part complete 264 | * @note : From update v20112022 we have only 1 part with index 0 265 | * @param ctx state context 266 | * @param action instance of DownloadFilePartCompleteAction 267 | */ 268 | @Debugger 269 | @Action(DownloadFilePartCompleteAction) 270 | downloadFilePartComplete( 271 | ctx: StateContext, 272 | action: DownloadFilePartCompleteAction 273 | ): void { 274 | const state = ctx.getState(); 275 | const channelId = this.store.selectSnapshot(AppSelectors.getChannelId); 276 | const file = state.localFiles.find((mfile: IFilePartSending) => mfile.fileId === action.fileId); 277 | this.signalingService 278 | .gettingPartComplete( 279 | channelId, 280 | file.fileId, 281 | action.fileIndex, 282 | file.totalPart 283 | ) 284 | .subscribe((res) => { 285 | const doneFile = state.localFiles.find(mfile => mfile.currentSize !== mfile.fileSize); 286 | if (doneFile) { 287 | ctx.dispatch(new TryToGettingFile()); 288 | } 289 | }); 290 | } 291 | 292 | /** 293 | * Close data channel 294 | * @param ctx state context 295 | * @param action instance of CloseReceiverDataChannelAction 296 | */ 297 | @Debugger 298 | @Action(CloseReceiverDataChannelAction) 299 | closeDataChannelAction( 300 | ctx: StateContext, 301 | action: CloseReceiverDataChannelAction 302 | ): void { 303 | this.signalingService.closeDataChannels(); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.action.ts: -------------------------------------------------------------------------------- 1 | import { EPeerState, IFilePartSending } from '@shared/app.model'; 2 | 3 | export class InitializeSignalingChannelAction { 4 | static readonly type = '[SENDER] InitializeSignalingChannelAction'; 5 | constructor() {} 6 | } 7 | 8 | export class DeleteFilesAction { 9 | static readonly type = '[SENDER] DeleteFilesAction'; 10 | constructor(public fileIndex: number) {} 11 | } 12 | 13 | export class AppendFilesAction { 14 | static readonly type = '[SENDER] DeleteFilesAction'; 15 | constructor(public files: IFilePartSending[]) {} 16 | } 17 | 18 | export class SenderResetStateToDefaultAction { 19 | static readonly type = '[SENDER] SenderResetStateToDefaultAction'; 20 | constructor() {} 21 | } 22 | 23 | export class UpdateFileIdAction { 24 | static readonly type = '[SENDER] UpdateFileIdAction'; 25 | constructor(public fileId: number, public newFileId: number) {} 26 | } 27 | 28 | 29 | export class UpdateFileSendingProgressAction { 30 | static readonly type = '[SENDER] UpdateFileSenderProgressAction'; 31 | constructor(public fileId: number, public increaseSize: number) {} 32 | } 33 | 34 | export class UpdateFileSendingCompletedAction { 35 | static readonly type = '[SENDER] UpdateFileSendingCompletedAction'; 36 | constructor(public peerId, public fileId: number, public filePart: number) {} 37 | } 38 | 39 | export class CannotConnectToPeer { 40 | static readonly type = '[SENDER] CannotConnectToPeer'; 41 | constructor(public fileId: number) {} 42 | } 43 | 44 | export class UpdateDataChannelStateAction { 45 | static readonly type = '[SENDER] UpdateDataChannelState'; 46 | constructor(public state: RTCDataChannelState) {} 47 | } 48 | 49 | export class SendDataAction { 50 | static readonly type = '[SENDER] SendDataAction'; 51 | constructor() {} 52 | } 53 | 54 | export class UpdateSenderStatusAction { 55 | static readonly type = '[SENDER] UpdatePeerStatus'; 56 | constructor(public peerState: EPeerState, public fileId: number) {} 57 | } 58 | 59 | export class CloseSenderDataChannelAction { 60 | static readonly type = '[SENDER] CloseDataChannelAction'; 61 | constructor() {} 62 | } -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 12 | 16 |
17 |
18 |
19 | Drop your files here 20 |
21 | 30 |
31 | or 32 | UPLOAD YOUR FILES 33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 | PLEASE SELECT AT LEAST 1 FILE... 42 |
43 | 44 |
45 |
46 | 52 |
53 |
54 |
55 |
56 |
57 | {{ files[0].fileName }} 58 |
59 | 63 | 64 |
65 |
66 | 73 |
74 |
75 |
76 |
77 |
78 | {{ files[1].fileName }} 79 |
80 | 84 | 85 |
86 |
87 | 94 |
95 |
96 |
97 |
98 |
99 | {{ files[2].fileName }} 100 |
101 | 105 | 106 |
107 |
108 | 115 |
116 |
117 | ID:{{ accessKey$ | async }}{{ channelId$ | async }}KEY: 121 | 126 | 137 | 147 | 157 | 158 |
159 |
160 |
161 |
162 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.component.scss: -------------------------------------------------------------------------------- 1 | // component css goes here 2 | $bg-image: url("/assets/images/icons_set.svg"); 3 | $bg-size: 625px 250px; 4 | 5 | ::ng-deep .ngx-file-drop__content { 6 | position: absolute; 7 | height: unset !important; 8 | } 9 | ::ng-deep .ngx-file-drop__drop-zone { 10 | border: unset !important; 11 | width: 356px; 12 | height: 358px !important; 13 | } 14 | 15 | ::ng-deep tui-stepper { 16 | @apply relative; 17 | top: 480px; 18 | height: 60px; 19 | button * { 20 | margin-left: auto !important; 21 | margin-right: auto !important; 22 | } 23 | button:nth-child(1) { 24 | @apply mr-auto; 25 | @apply ml-10; 26 | } 27 | button:nth-child(2) { 28 | @apply mx-auto; 29 | } 30 | button:nth-child(3) { 31 | @apply ml-auto; 32 | @apply mr-6; 33 | } 34 | } 35 | .e2_22 { 36 | background-image: $bg-image; 37 | background-size: $bg-size; 38 | background-position-x: -375px; 39 | background-position-y: 0px; 40 | } 41 | .e103_20 { 42 | top: 396px; 43 | } 44 | .e1_54 { 45 | top: 435px; 46 | } 47 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewInit, 3 | ChangeDetectionStrategy, 4 | Component, 5 | OnDestroy, 6 | ViewChild 7 | } from '@angular/core'; 8 | import { Router } from '@angular/router'; 9 | import { Select, Store } from '@ngxs/store'; 10 | import { AccessChannelAction, SetScreenAction } from '@shared/app.action'; 11 | import { IFilePartSending } from '@shared/app.model'; 12 | import { AppSelectors } from '@shared/app.selector'; 13 | import { SharedAppService } from '@shared/shared-app.service'; 14 | import { TuiStepperComponent } from '@taiga-ui/kit'; 15 | import { 16 | OnDestroyMixin 17 | } from '@w11k/ngx-componentdestroyed'; 18 | import { 19 | FileSystemDirectoryEntry, 20 | FileSystemFileEntry, 21 | NgxFileDropEntry 22 | } from 'ngx-file-drop'; 23 | import { Observable } from 'rxjs'; 24 | import { 25 | AppendFilesAction, 26 | CloseSenderDataChannelAction, 27 | DeleteFilesAction, 28 | InitializeSignalingChannelAction, 29 | SendDataAction, 30 | SenderResetStateToDefaultAction 31 | } from './sender.action'; 32 | import { SenderSelectors } from './sender.selector'; 33 | 34 | @Component({ 35 | selector: 'et-sender', 36 | templateUrl: 'sender.component.html', 37 | styleUrls: ['sender.component.scss'], 38 | changeDetection: ChangeDetectionStrategy.OnPush, 39 | }) 40 | export class SenderComponent 41 | extends OnDestroyMixin 42 | implements AfterViewInit, OnDestroy 43 | { 44 | public files: NgxFileDropEntry[] = []; 45 | showGuide = false; 46 | browseDisable = false; 47 | @Select(AppSelectors.getChannelId) channelId$: Observable; 48 | @Select(AppSelectors.getAccessKey) accessKey$: Observable; 49 | @Select(SenderSelectors.getLocalFiles) files$: Observable; 50 | @Select(SenderSelectors.getAllStep) steps$: Observable; 51 | @Select(SenderSelectors.getCurrentStep) currentStep$: Observable; 52 | 53 | @ViewChild('stepper') stepper: TuiStepperComponent; 54 | 55 | constructor( 56 | private router: Router, 57 | private store: Store, 58 | private commonService: SharedAppService 59 | ) { 60 | super(); 61 | this.store.dispatch(new SenderResetStateToDefaultAction()); 62 | this.store.dispatch(new AccessChannelAction(null, null)); 63 | 64 | this.listenEvent(); 65 | } 66 | ngAfterViewInit(): void { 67 | this.store.dispatch(new SetScreenAction('sender')); 68 | } 69 | 70 | ngOnDestroy(): void { 71 | this.store.dispatch(new CloseSenderDataChannelAction()); 72 | super.ngOnDestroy(); 73 | } 74 | 75 | /** 76 | * When data channel is ready, send the file 77 | */ 78 | listenEvent(): void { 79 | this.store 80 | .select(SenderSelectors.getDataChannelState) 81 | // .pipe(untilComponentDestroyed(this)) 82 | .subscribe((state) => { 83 | if (state === 'open') { 84 | this.store.dispatch(new SendDataAction()); 85 | } 86 | }); 87 | 88 | this.store 89 | .select(SenderSelectors.isAllReceiverDone) 90 | // .pipe(untilComponentDestroyed(this)) 91 | .subscribe((done) => { 92 | if (done) { 93 | this.commonService 94 | .showNotifyAskUserConfirm( 95 | 'Your friends were downloading completely! You can close the tab now!' 96 | ) 97 | .subscribe((res) => { 98 | this.router.navigateByUrl('/').then((_) => { 99 | window.location.reload(); 100 | }); 101 | }); 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * Start the game 108 | * @trigger when user click on step 1 109 | */ 110 | initChannel(): void { 111 | const files = this.store.selectSnapshot( 112 | SenderSelectors.getLocalFiles 113 | ); 114 | if (files && files.length > 0) { 115 | this.showGuide = false; 116 | this.browseDisable = true; 117 | this.commonService.showNotify( 118 | 'Wait until step2 complete then share \'link\' or \'id | key\' to your friends ', 119 | 'Initialize Channel', 120 | 3000 121 | ); 122 | this.store.dispatch(new InitializeSignalingChannelAction()); 123 | } else { 124 | this.stepper.activeItemIndex = -1; 125 | this.commonService 126 | .showDialog('Please select atleast 1 file..') 127 | .subscribe(); 128 | } 129 | } 130 | 131 | /** 132 | * Mapping from browser file to system file 133 | * @param files list of input file 134 | * @returns list of file 135 | */ 136 | fileMapping(files: FileList | File[]): IFilePartSending[] { 137 | let mapList: File[] = []; 138 | if (files instanceof FileList) { mapList = Array.from(files); } 139 | else { mapList = files; } 140 | const filesMap: IFilePartSending[] = mapList.map((file, index) => { 141 | return { 142 | fileId: index, 143 | fileName: file.name, 144 | currentSize: 0, 145 | status: 0, 146 | fileData: file, 147 | totalPart: 1, 148 | fileSize: file.size, 149 | index: 0, 150 | }; 151 | }); 152 | return filesMap; 153 | } 154 | 155 | /** 156 | * On press button close in list of file 157 | * @param index: number 158 | */ 159 | onClose(index: number): void { 160 | this.store.dispatch(new DeleteFilesAction(index)); 161 | } 162 | 163 | /** 164 | * ON drag and drop a file on the list 165 | * @param files : files change 166 | */ 167 | dropped(files: NgxFileDropEntry[]): void { 168 | this.files = files; 169 | if (files.length > 0) { 170 | this.showGuide = true; 171 | } 172 | for (const droppedFile of files) { 173 | // Is it a file? 174 | if (droppedFile.fileEntry.isFile) { 175 | const fileEntry = droppedFile.fileEntry as FileSystemFileEntry; 176 | fileEntry.file((file: File) => { 177 | // Here you can access the real file 178 | this.store.dispatch(new AppendFilesAction(this.fileMapping([file]))); 179 | }); 180 | } else { 181 | // It was a directory (empty directories are added, otherwise only files) 182 | const fileEntry = droppedFile.fileEntry as FileSystemDirectoryEntry; 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * Debug event 189 | * @param event from DOM 190 | */ 191 | public fileOver(event): void { 192 | console.log(event); 193 | } 194 | 195 | /** 196 | * Debug event 197 | * @param event from DOM 198 | */ 199 | public fileLeave(event): void{ 200 | console.log(event); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.module.ts: -------------------------------------------------------------------------------- 1 | // Angular Imports 2 | import { AsyncPipe, CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterModule } from '@angular/router'; 6 | import { NgxsModule } from '@ngxs/store'; 7 | import { FileNamePipe } from '@shared/file-name.pipe'; 8 | import { SharedAppModule } from '@shared/shared-app.module'; 9 | import { 10 | TuiButtonModule, 11 | TuiDialogModule, 12 | TuiRootModule 13 | } from '@taiga-ui/core'; 14 | import { 15 | TuiMarkerIconModule, 16 | TuiStepperComponent, 17 | TuiStepperModule 18 | } from '@taiga-ui/kit'; 19 | import { ProgressBarModule } from 'angular-progress-bar'; 20 | import { NgxFileDropModule } from 'ngx-file-drop'; 21 | // This Module's Components 22 | import { SenderComponent } from './sender.component'; 23 | import { SenderState } from './sender.state'; 24 | 25 | @NgModule({ 26 | imports: [ 27 | CommonModule, 28 | RouterModule.forChild([ 29 | { 30 | path: '', 31 | component: SenderComponent, 32 | }, 33 | ]), 34 | NgxsModule.forFeature([SenderState]), 35 | TuiRootModule, 36 | TuiMarkerIconModule, 37 | TuiButtonModule, 38 | TuiStepperModule, 39 | TuiDialogModule, 40 | NgxFileDropModule, 41 | FormsModule, 42 | ReactiveFormsModule, 43 | ProgressBarModule, 44 | SharedAppModule, 45 | ], 46 | providers: [ 47 | AsyncPipe, 48 | TuiStepperComponent, 49 | FileNamePipe 50 | ], 51 | declarations: [SenderComponent], 52 | exports: [], 53 | }) 54 | export class SenderModule {} 55 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/sender/sender.selector.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | import { SenderState, SenderStateModel } from './sender.state'; 3 | import {IFilePartSending, ISteps} from '@shared/app.model'; 4 | 5 | export class SenderSelectors { 6 | @Selector([SenderState]) 7 | static getAllStep(state: SenderStateModel): ISteps[] { 8 | return state.steps; 9 | } 10 | 11 | @Selector([SenderState]) 12 | static getCurrentStep(state: SenderStateModel): number { 13 | return state.currentStep; 14 | } 15 | 16 | @Selector([SenderState]) 17 | static getLocalFiles(state: SenderStateModel): IFilePartSending[] { 18 | return state.localFiles.filter((file: IFilePartSending) => file.fileId !== null); 19 | } 20 | 21 | @Selector([SenderState]) 22 | static getDataChannelState(state: SenderStateModel): RTCDataChannelState { 23 | return state.dataChannelState; 24 | } 25 | 26 | @Selector([SenderState]) 27 | static isAllReceiverDone(state: SenderStateModel): boolean { 28 | const peersCompleted = state.peersCompleted; 29 | for (const peerId in peersCompleted) { 30 | if (peersCompleted[peerId].length < state.localFiles.length) { 31 | return false; 32 | } 33 | } 34 | return state.localFiles.length > 0 && Object.keys(peersCompleted).length > 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/services/constant.ts: -------------------------------------------------------------------------------- 1 | export const STUN_SERVER = [ 2 | { urls: 'stun:stun.l.google.com:19302' }, 3 | { urls: 'stun:stun1.l.google.com:19302' }, 4 | { urls: 'stun:stun2.l.google.com:19302' }, 5 | { urls: 'stun:stun3.l.google.com:19302' }, 6 | { urls: 'stun:stun4.l.google.com:19302' }, 7 | { 8 | urls: 'stun:openrelay.metered.ca:80', 9 | }, 10 | ]; 11 | 12 | export const TURN_SERVER = [ 13 | { 14 | urls: 'turn:openrelay.metered.ca:80', 15 | username: 'openrelayproject', 16 | credential: 'openrelayproject', 17 | }, 18 | { 19 | urls: 'turn:openrelay.metered.ca:443', 20 | username: 'openrelayproject', 21 | credential: 'openrelayproject', 22 | }, 23 | { 24 | urls: 'turn:openrelay.metered.ca:443?transport=tcp', 25 | username: 'openrelayproject', 26 | credential: 'openrelayproject', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/services/rx-stomp-bridge.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Store } from '@ngxs/store'; 3 | import { SetRxConnectStateAction } from '@shared/app.action'; 4 | import { ISignalingMessage, RxStompState } from '@shared/app.model'; 5 | import { AppSelectors } from '@shared/app.selector'; 6 | import { RxStompService } from '@stomp/ng2-stompjs'; 7 | import { IMessage } from '@stomp/stompjs'; 8 | import { Observable } from 'rxjs'; 9 | import { map, share } from 'rxjs/operators'; 10 | 11 | @Injectable({ 12 | providedIn: 'root', 13 | }) 14 | export class RxStompBridgeService { 15 | // tslint:disable-next-line:variable-name 16 | private _rxMessage$: Observable; 17 | 18 | constructor(private store: Store, private rxStompService: RxStompService) { 19 | this.store.select(AppSelectors.getPeerId).subscribe((peerId) => { 20 | if (peerId) { 21 | this._rxMessage$ = this.rxStompService 22 | .watch(`PEERS.B${peerId}`) 23 | .pipe(share()); 24 | } 25 | }); 26 | 27 | this.rxStompService.connectionState$ 28 | .pipe( 29 | map((state) => { 30 | // convert numeric RxStompState to string 31 | return state as RxStompState; 32 | }) 33 | ) 34 | .subscribe((res) => { 35 | console.log('SetRxConnectStateAction', res); 36 | this.store.dispatch(new SetRxConnectStateAction(res)); 37 | }); 38 | } 39 | 40 | /** 41 | * Subscribe Signaling Channel (ActiveMQ) 42 | * @returns Observable 43 | */ 44 | getRxMessage$(): Observable { 45 | return this._rxMessage$; 46 | } 47 | 48 | /** 49 | * Send a message through Signaling Channel (ActiveMQ) 50 | * @param message The message 51 | * @param connectingPeerId peerId of receiver 52 | */ 53 | publish(message: ISignalingMessage, connectingPeerId: number): void { 54 | console.log(`SEND message ${message.content} to ${message.to}`, message); 55 | this.rxStompService.publish({ 56 | destination: `PEERS.B${connectingPeerId}`, 57 | body: JSON.stringify(message), 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/services/rx-stomp.config.ts: -------------------------------------------------------------------------------- 1 | import { InjectableRxStompConfig } from '@stomp/ng2-stompjs'; 2 | 3 | export const RTCRxStompConfig: InjectableRxStompConfig = { 4 | // Which server? 5 | brokerURL: 6 | 'wss://activemq.mindstone.cc?maximumConnections=1000&wireFormat.maxFrameSize=104857600', 7 | 8 | // Headers 9 | // Typical keys: login, passcode, host 10 | connectHeaders: { 11 | login: 'client', 12 | passcode: 'yourpassword', 13 | }, 14 | 15 | // How often to heartbeat? 16 | // Interval in milliseconds, set to 0 to disable 17 | heartbeatIncoming: 0, // Typical value 0 - disabled 18 | heartbeatOutgoing: 20000, // Typical value 20000 - every 20 seconds 19 | 20 | // Wait in milliseconds before attempting auto reconnect 21 | // Set to 0 to disable 22 | // Typical value 500 (500 milli seconds) 23 | reconnectDelay: 1000, 24 | 25 | // Will log diagnostics on console 26 | // It can be quite verbose, not recommended in production 27 | // Skip this key to stop logging to console 28 | debug: (msg: string): void => { 29 | // console.log(msg); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/app.action.ts: -------------------------------------------------------------------------------- 1 | import { RxStompState } from './app.model'; 2 | 3 | export class SetScreenAction { 4 | static readonly type = '[APP] Set screen'; 5 | constructor(public screen: 'receiver' | 'sender') {} 6 | } 7 | 8 | export class SetPeerIdAction { 9 | static readonly type = '[APP] Set peer Id'; 10 | constructor(public peerId: number) {} 11 | } 12 | 13 | export class AccessChannelAction { 14 | static readonly type = '[APP] Access channel'; 15 | constructor(public channelId: string, public accessKey: string) {} 16 | } 17 | 18 | export class SetRxConnectStateAction { 19 | static readonly type = '[APP] RxConnectReady'; 20 | constructor(public state: RxStompState) {} 21 | } 22 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/app.model.ts: -------------------------------------------------------------------------------- 1 | export enum RxStompState { 2 | CONNECTING = 0, 3 | OPEN = 1, 4 | CLOSING = 2, 5 | CLOSED = 3, 6 | } 7 | 8 | // tslint:disable-next-line:class-name 9 | export interface _TInstanceState { 10 | connectingPeerId: number; 11 | localFiles: IFilePartSending[]; 12 | peersCompleted: { [peerId: string]: string[] }; 13 | dataChannelState: RTCDataChannelState; 14 | } 15 | 16 | export interface IFilePartInformation { 17 | fileId: number; 18 | fileName: string; 19 | fileSize: number; 20 | index: number; 21 | totalPart: number; 22 | } 23 | 24 | // tslint:disable-next-line:class-name 25 | export interface _IFilePartSending extends IFilePartInformation { 26 | status: number; 27 | currentSize: number; 28 | fileData: File; 29 | } 30 | export interface IFilePartSending extends Partial<_IFilePartSending> {} 31 | 32 | // Initialize model ================ 33 | // tslint:disable-next-line:class-name 34 | export interface _InitChannelResDTO { 35 | channelId: string; 36 | accessKey: string; 37 | } 38 | export interface IInitChannelResDTO extends Partial<_InitChannelResDTO> {} 39 | 40 | // tslint:disable-next-line:class-name 41 | export interface _InitChannelReqDTO { 42 | peerId: number; 43 | } 44 | export interface IInitChannelReqDTO extends Partial<_InitChannelReqDTO> {} 45 | // ================================= 46 | 47 | // tslint:disable-next-line:class-name 48 | export interface _FilePart { 49 | ownerId: number; 50 | fileId: number; 51 | index: number; // From 20-11-2022 Now we have only 1 file part with index 0 52 | channelId: number; 53 | totalPart: number; 54 | state: 'NOT_AVAILABLE' | 'TAKING' | 'AVAILABLE'; 55 | } 56 | export interface FilePart extends Partial<_FilePart> {} 57 | export type Isingal = 58 | | 'list-files' 59 | | 'preflight' 60 | | 'webrtc-offer' 61 | | 'webrtc-answer' 62 | | 'webrtc-ice-candidate' 63 | | 'start-sharing-ice' 64 | | 'webrtc-close-channel' 65 | | 'get-file-completed' 66 | | 'get-file-failed'; 67 | 68 | /** 69 | * Common signaling message model, which send between sender and receiver 70 | * @content : list-file ==> @data = accessKey string 71 | * @content : offer ==> @data = { fileId; partIndex } 72 | * @content : answer ==> @data = RTCSessionDescriptionInit 73 | * @content : iceCandidate ==> @data = RTCIceCandidateInit 74 | * @content : get-file-completed ==> @data = { fileId; partIndex } 75 | */ 76 | // tslint:disable-next-line:class-name 77 | export interface _ISignalingMessage { 78 | from: number; 79 | to: number; 80 | content: Isingal; 81 | data: any; 82 | info: IFilePartInformation; 83 | } 84 | export interface ISignalingMessage extends Partial<_ISignalingMessage> {} 85 | export class SignalingMessage implements _ISignalingMessage { 86 | from: number; 87 | to: number; 88 | content: Isingal; 89 | data: any; 90 | info: IFilePartInformation; 91 | constructor( 92 | from: number, 93 | to: number, 94 | content: Isingal, 95 | data: any, 96 | info: IFilePartInformation = null 97 | ) { 98 | this.from = from; 99 | this.to = to; 100 | this.content = content; 101 | this.data = data; 102 | this.info = info; 103 | } 104 | } 105 | 106 | /** 107 | * PreFlight message model to ask sender = 108 | * Do you have this file id? 109 | */ 110 | export interface IPreFlightModel { 111 | fileId: number; 112 | partIndex: number; 113 | } 114 | 115 | /** 116 | * State of a peer - a browser 117 | */ 118 | export enum EPeerState { 119 | IDLE = 'IDLE', 120 | TAKING = 'TAKING', 121 | SENDING = 'SENDING', 122 | } 123 | 124 | /** 125 | * State of step which display on the screen 126 | */ 127 | export interface ISteps{ 128 | state: 'normal' | 'pass' | 'error'; 129 | disable: boolean; 130 | name: string; 131 | } 132 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/app.selector.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | import { RxStompState } from './app.model'; 3 | import { AppState, AppStateModel } from './app.state'; 4 | 5 | export class AppSelectors { 6 | @Selector([AppState]) 7 | static getScreen(state: AppStateModel) { 8 | return state.screen; 9 | } 10 | 11 | @Selector([AppState]) 12 | static getPeerId(state: AppStateModel) { 13 | return state.peerId; 14 | } 15 | 16 | @Selector([AppState]) 17 | static getChannelId(state: AppStateModel) { 18 | return state.channelId; 19 | } 20 | 21 | @Selector([AppState]) 22 | static getAccessKey(state: AppStateModel) { 23 | return state.accessKey; 24 | } 25 | 26 | @Selector([AppState]) 27 | static isReadyToSend(state: AppStateModel) { 28 | return !!state.channelId && !!state.accessKey && state.screen == 'sender'; 29 | } 30 | 31 | @Selector([AppState]) 32 | static isReadyToReceive(state: AppStateModel) { 33 | return ( 34 | !!state.peerId && 35 | !!state.channelId && 36 | !!state.accessKey && 37 | state.screen == 'receiver' && 38 | state.rxStompState === RxStompState.OPEN 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/app.state.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Action, State, StateContext, Store } from '@ngxs/store'; 3 | import produce from 'immer'; 4 | import { 5 | AccessChannelAction, 6 | SetPeerIdAction, 7 | SetRxConnectStateAction, 8 | SetScreenAction, 9 | } from './app.action'; 10 | import { RxStompState } from './app.model'; 11 | import { Debugger } from './debug.decorator'; 12 | 13 | interface _AppStateModel { 14 | screen: 'sender' | 'receiver'; 15 | peerId: number; 16 | accessKey: string; 17 | channelId: string; 18 | rxStompState: RxStompState; 19 | } 20 | 21 | export interface AppStateModel extends Partial<_AppStateModel> {} 22 | 23 | @State({ 24 | name: 'appState', 25 | defaults: {}, 26 | }) 27 | @Injectable() 28 | export class AppState { 29 | constructor(private store: Store) {} 30 | 31 | @Debugger 32 | @Action(SetPeerIdAction) 33 | setPeerId(ctx: StateContext, action: SetPeerIdAction) { 34 | ctx.setState( 35 | produce((draft) => { 36 | draft.peerId = action.peerId; 37 | }) 38 | ); 39 | } 40 | 41 | @Debugger 42 | @Action(AccessChannelAction) 43 | setChannelId(ctx: StateContext, action: AccessChannelAction) { 44 | ctx.setState( 45 | produce((draft) => { 46 | draft.channelId = action.channelId; 47 | draft.accessKey = action.accessKey; 48 | }) 49 | ); 50 | } 51 | 52 | @Debugger 53 | @Action(SetScreenAction) 54 | setScreen(ctx: StateContext, action: SetScreenAction) { 55 | ctx.setState( 56 | produce((draft) => { 57 | draft.screen = action.screen; 58 | }) 59 | ); 60 | } 61 | 62 | @Debugger 63 | @Action(SetRxConnectStateAction) 64 | setRxConnectStateAction(ctx: StateContext, action: SetRxConnectStateAction) { 65 | ctx.setState( 66 | produce((draft) => { 67 | draft.rxStompState = action.state; 68 | }) 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/debug.decorator.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:typedef 2 | export function Debugger( 3 | target: object, 4 | propertyKey: string, 5 | descriptor: PropertyDescriptor 6 | ) { 7 | // Again, cache the original method for later use 8 | const originalMethod = descriptor.value; 9 | // we write a new implementation for the method 10 | descriptor.value = async function(...args) { 11 | const proto = Object.getPrototypeOf(args[1]); 12 | console.log(`%c${proto.constructor.type}`, 'color:green; font-size:16px;'); 13 | // we run the original method with the original arguments 14 | const result = originalMethod.apply(this, args); 15 | 16 | // and return the result 17 | return result; 18 | }; 19 | return descriptor; 20 | } 21 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/file-name.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'fileName', 5 | }) 6 | export class FileNamePipe implements PipeTransform { 7 | transform(value: string, ...args: unknown[]): string { 8 | let res = ''; 9 | if ( 10 | /\.png$/.test(value.valueOf()) || 11 | /\.jpg$/.test(value.valueOf()) || 12 | /\.svg$/.test(value.valueOf()) || 13 | /\.bitmap$/.test(value.valueOf()) || 14 | /\.icn$/.test(value.valueOf()) 15 | ) { 16 | res += 'picture-icn '; 17 | } else if ( 18 | /\.rar$/.test(value.valueOf()) || 19 | /\.zip$/.test(value.valueOf()) || 20 | /\.rip$/.test(value.valueOf()) || 21 | /\.7z$/.test(value.valueOf()) 22 | ) { 23 | res += 'rar-icn '; 24 | } else { 25 | res += 'doc-icn '; 26 | } 27 | return res; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/shared-app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FileNamePipe } from './file-name.pipe'; 3 | import { YesNoDialogComponent } from './yes-no-dialog/yes-no-dialog.component'; 4 | 5 | 6 | @NgModule({ 7 | declarations: [FileNamePipe, YesNoDialogComponent], 8 | imports: [ 9 | ], 10 | providers: [ 11 | FileNamePipe 12 | ], 13 | exports:[FileNamePipe] 14 | }) 15 | export class SharedAppModule {} 16 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/shared-app.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Injector } from '@angular/core'; 2 | import { YesNoDialogComponent } from '@shared/yes-no-dialog/yes-no-dialog.component'; 3 | import { 4 | TuiDialogContext, 5 | TuiDialogService, 6 | TuiNotification, 7 | TuiNotificationsService, 8 | } from '@taiga-ui/core'; 9 | import { 10 | PolymorpheusComponent, 11 | PolymorpheusTemplate, 12 | } from '@tinkoff/ng-polymorpheus'; 13 | import {Observable} from 'rxjs'; 14 | 15 | @Injectable({ 16 | providedIn: 'root', 17 | }) 18 | export class SharedAppService { 19 | constructor( 20 | @Inject(TuiDialogService) private readonly dialogService: TuiDialogService, 21 | @Inject(TuiNotificationsService) 22 | private readonly notificationsService: TuiNotificationsService, 23 | @Inject(Injector) private injector: Injector 24 | ) {} 25 | 26 | /** 27 | * Show dialog 28 | * @param message message 29 | * @param header dialog header 30 | * @param size dialog size 31 | */ 32 | showDialog( 33 | message: string = 'If something goes wrong, it means I forgot to pay a bill. Sorry about that. LOL', 34 | header: string = 'Error', 35 | size: 's' | 'm' | 'l' = 's' 36 | ): Observable { 37 | return this.dialogService.open(message, { 38 | label: header, 39 | size, 40 | closeable: true, 41 | dismissible: true, 42 | }); 43 | } 44 | 45 | /** 46 | * An elegant way to show a dialog 47 | * @param content Taiga context 48 | */ 49 | showDialogWithTemplate(content: PolymorpheusTemplate): void { 50 | this.dialogService.open(content).subscribe(); 51 | } 52 | 53 | /** 54 | * Show notify 55 | * @param content notify content 56 | * @param heading notify header 57 | * @param autoClose does it auto close? 58 | */ 59 | showNotify(content: string, heading: string, autoClose: number = -1): void { 60 | this.notificationsService 61 | .show(content.valueOf(), { 62 | label: heading.valueOf(), 63 | autoClose: autoClose < 0 ? false : autoClose, 64 | hasCloseButton: true, 65 | }) 66 | .subscribe(); 67 | } 68 | 69 | /** 70 | * Show notify ask user confirm 71 | * @param messageContent string content 72 | * @returns Observable 73 | */ 74 | showNotifyAskUserConfirm(messageContent: string): Observable { 75 | return this.notificationsService.show( 76 | new PolymorpheusComponent(YesNoDialogComponent, this.injector), 77 | { 78 | data: messageContent, 79 | label: 'Attention', 80 | status: TuiNotification.Warning, 81 | autoClose: false, 82 | } 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /f2f-frontend/src/app/shared/yes-no-dialog/yes-no-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | import { TuiNotificationContentContext } from '@taiga-ui/core'; 3 | import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'; 4 | 5 | @Component({ 6 | selector: 'et-yes-no-dialog', 7 | template: ` 8 |
16 |
{{ message }}
17 |
21 | Ok 22 |
23 |
24 | `, 25 | styles: [], 26 | }) 27 | export class YesNoDialogComponent { 28 | message = ''; 29 | constructor( 30 | @Inject(POLYMORPHEUS_CONTEXT) 31 | private readonly context: TuiNotificationContentContext 32 | ) { 33 | this.message = context.data; 34 | } 35 | 36 | ok(): void { 37 | this.context.emitAndCloseHook(true); 38 | } 39 | 40 | cancel(): void { 41 | this.context.emitAndCloseHook(false); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /f2f-frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /f2f-frontend/src/assets/images/IMG_1148.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-frontend/src/assets/images/IMG_1148.JPG -------------------------------------------------------------------------------- /f2f-frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | import { IEnvironment } from "./envrionment.model"; 2 | 3 | export const environment: IEnvironment = { 4 | production: false, 5 | // API_HOST: 'https://7rhpjdl0sg.execute-api.us-east-2.amazonaws.com', 6 | API_HOST: 'https://f2f-api.mindstone.cc', 7 | EV_PATH: 'v1', 8 | CHANNEL_PATH: 'channel', 9 | FILE_PATH: 'file', 10 | INITIALIZE_PATH: 'channel/initialize', 11 | PEER_PATH: 'peer' 12 | }; 13 | -------------------------------------------------------------------------------- /f2f-frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | import { IEnvironment } from './envrionment.model'; 6 | 7 | export const environment: IEnvironment = { 8 | production: false, 9 | // API_HOST: 'https://7rhpjdl0sg.execute-api.us-east-2.amazonaws.com', 10 | API_HOST: 'http://localhost:8080', 11 | // API_HOST: 'https://f2f-api.mindstone.cc', 12 | EV_PATH: 'v1', 13 | CHANNEL_PATH: 'channel', 14 | FILE_PATH: 'file', 15 | INITIALIZE_PATH: 'channel/initialize', 16 | PEER_PATH: 'peer' 17 | }; 18 | 19 | /* 20 | * For easier debugging in development mode, you can import the following file 21 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 22 | * 23 | * This import should be commented out in production mode because it will have a negative impact 24 | * on performance if an error is thrown. 25 | */ 26 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 27 | -------------------------------------------------------------------------------- /f2f-frontend/src/environments/envrionment.model.ts: -------------------------------------------------------------------------------- 1 | export interface IEnvironment { 2 | production: boolean; 3 | API_HOST: string; 4 | EV_PATH: string; 5 | CHANNEL_PATH: string; 6 | FILE_PATH: string; 7 | INITIALIZE_PATH: string; 8 | PEER_PATH: string; 9 | }; 10 | -------------------------------------------------------------------------------- /f2f-frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhvu95/angular-send-file-peer-to-peer/9557fbdcf799a66fdca5bd1832135c0a40e83ea5/f2f-frontend/src/favicon.ico -------------------------------------------------------------------------------- /f2f-frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /f2f-frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /f2f-frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js'; // Included with Angular CLI. 59 | // rtc peer connection patch 60 | import 'zone.js/dist/webapis-rtc-peer-connection'; 61 | // rtc peer connection patch 62 | // import 'zone.js/dist/zone-patch-socket-io'; 63 | // import 'zone.js/dist/zone-patch-message-port.js' 64 | // getUserMedia patch 65 | // import 'zone.js/dist/zone-patch-user-media'; 66 | 67 | /*************************************************************************************************** 68 | * APPLICATION IMPORTS 69 | */ 70 | 71 | /*************************************************************************************************** 72 | * SCULLY IMPORTS 73 | */ 74 | // tslint:disable-next-line: align 75 | import 'zone.js/dist/task-tracking'; -------------------------------------------------------------------------------- /f2f-frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "~@taiga-ui/core/styles/taiga-ui-local"; 3 | 4 | @import "tailwindcss/base"; 5 | @import "tailwindcss/components"; 6 | @import "tailwindcss/utilities"; 7 | 8 | $roboto-font: "Roboto"; 9 | 10 | $bg-image: url("/assets/images/icons_set.svg"); 11 | $bg-size: 500px 200px; 12 | body { 13 | // background-color: #afc2d9; 14 | background-color: #8ec5fc; 15 | background-image: linear-gradient(62deg, #8ec5fc 0%, #e0c3fc 100%); 16 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 17 | margin: 0px; 18 | } 19 | 20 | * { 21 | font-family: $roboto-font !important; 22 | } 23 | button:focus-visible, 24 | button:focus { 25 | outline: none; 26 | } 27 | :root { 28 | --tui-primary: rgba( 29 | 127.00000002980232, 30 | 126.00000008940697, 31 | 217.0000022649765, 32 | 1 33 | ) !important; 34 | --tui-primary-hover: rgba(128, 126, 217, 0.808) !important; 35 | --tui-text-font: var(roboto-font); 36 | } 37 | 38 | .button-ok { 39 | background-color: #e6950b; 40 | border-radius: 5px; 41 | width: 50px; 42 | cursor: pointer; 43 | border: 1px solid #b35b09; 44 | text-align: center; 45 | } 46 | .button-ok:hover { 47 | background-color: #f1b753; 48 | } 49 | .button-ok:active { 50 | background-color: #ff7b00; 51 | } 52 | 53 | .ellipsis { 54 | @apply overflow-ellipsis; 55 | @apply overflow-hidden; 56 | @apply whitespace-nowrap; 57 | } 58 | 59 | .add-icn { 60 | background-image: $bg-image; 61 | background-size: $bg-size; 62 | background-position-x: -250px; 63 | background-position-y: -100px; 64 | } 65 | .picture-icn { 66 | background-image: $bg-image; 67 | background-size: $bg-size; 68 | background-position-x: -250px; 69 | background-position-y: -0px; 70 | } 71 | .rar-icn { 72 | background-image: $bg-image; 73 | background-size: $bg-size; 74 | background-position-x: -250px; 75 | background-position-y: -50px; 76 | } 77 | .doc-icn { 78 | background-image: $bg-image; 79 | background-size: $bg-size; 80 | background-position-x: -250px; 81 | background-position-y: -150px; 82 | } 83 | -------------------------------------------------------------------------------- /f2f-frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /f2f-frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [ 3 | './src/**/*.ts', 4 | './src/**/*.html', 5 | './src/**/*.scss', 6 | ], 7 | darkMode: false, // or 'media' or 'class' 8 | important: true, 9 | theme: { 10 | extend: {}, 11 | }, 12 | variants: { 13 | extend: {}, 14 | }, 15 | plugins: [], 16 | } 17 | -------------------------------------------------------------------------------- /f2f-frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /f2f-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ], 19 | "paths": { 20 | "@shared/*": ["./src/app/shared/*"], 21 | "@services/*": ["./src/app/services/*"], 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /f2f-frontend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /f2f-frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "et", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "et", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef": [ 98 | true, 99 | "call-signature" 100 | ], 101 | "typedef-whitespace": { 102 | "options": [ 103 | { 104 | "call-signature": "nospace", 105 | "index-signature": "nospace", 106 | "parameter": "nospace", 107 | "property-declaration": "nospace", 108 | "variable-declaration": "nospace" 109 | }, 110 | { 111 | "call-signature": "onespace", 112 | "index-signature": "onespace", 113 | "parameter": "onespace", 114 | "property-declaration": "onespace", 115 | "variable-declaration": "onespace" 116 | } 117 | ] 118 | }, 119 | "variable-name": { 120 | "options": [ 121 | "ban-keywords", 122 | "check-format", 123 | "allow-pascal-case" 124 | ] 125 | }, 126 | "whitespace": { 127 | "options": [ 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type", 133 | "check-typecast" 134 | ] 135 | }, 136 | "no-conflicting-lifecycle": true, 137 | "no-host-metadata-property": true, 138 | "no-input-rename": true, 139 | "no-inputs-metadata-property": true, 140 | "no-output-native": true, 141 | "no-output-on-prefix": true, 142 | "no-output-rename": true, 143 | "no-outputs-metadata-property": true, 144 | "template-banana-in-box": true, 145 | "template-no-negated-async": true, 146 | "use-lifecycle-interface": true, 147 | "use-pipe-transform-interface": true 148 | }, 149 | "rulesDirectory": [ 150 | "codelyzer" 151 | ] 152 | } -------------------------------------------------------------------------------- /f2f-frontend/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from 'webpack'; 2 | 3 | export default { 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.scss$/i, 8 | loader: 'postcss-loader', 9 | options: { 10 | postcssOptions: { 11 | ident: 'postcss', 12 | syntax: 'postcss-scss', 13 | plugins: () => [ 14 | require('postcss-import'), 15 | require('tailwindcss'), 16 | require('postcss-nested'), 17 | require('autoprefixer'), 18 | ], 19 | }, 20 | }, 21 | }, 22 | ], 23 | }, 24 | } as Configuration; 25 | --------------------------------------------------------------------------------