├── api ├── requirements.txt └── main.py ├── deploy ├── Caddyfile └── docker-compose.yml ├── web ├── assets │ ├── comic.png │ ├── icon.png │ ├── filesync.png │ └── comic-dark.png ├── js │ ├── modules │ │ ├── webrtc │ │ │ ├── turn.js │ │ │ ├── file.js │ │ │ └── user.js │ │ ├── dom.js │ │ └── script.js │ └── vendors │ │ ├── qrious.min.js │ │ └── crypto-js.min.js ├── css │ └── style.css ├── test.html └── index.html ├── LICENSE ├── nginx.conf ├── .github └── workflows │ └── release.yml ├── Dockerfile └── README.md /api/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi[standard] 2 | PyJWT -------------------------------------------------------------------------------- /deploy/Caddyfile: -------------------------------------------------------------------------------- 1 | :80 { 2 | reverse_proxy filesync:80 3 | } -------------------------------------------------------------------------------- /web/assets/comic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polius/FileSync/HEAD/web/assets/comic.png -------------------------------------------------------------------------------- /web/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polius/FileSync/HEAD/web/assets/icon.png -------------------------------------------------------------------------------- /web/assets/filesync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polius/FileSync/HEAD/web/assets/filesync.png -------------------------------------------------------------------------------- /web/assets/comic-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polius/FileSync/HEAD/web/assets/comic-dark.png -------------------------------------------------------------------------------- /deploy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | caddy: 3 | image: caddy 4 | container_name: filesync-caddy 5 | ports: 6 | - "80:80" 7 | - "443:443" 8 | volumes: 9 | - ./Caddyfile:/etc/caddy/Caddyfile:ro 10 | - filesync:/data 11 | restart: unless-stopped 12 | 13 | coturn: 14 | image: coturn/coturn:alpine 15 | container_name: filesync-coturn 16 | command: 17 | - --fingerprint 18 | - --use-auth-secret 19 | - --no-multicast-peers 20 | - --realm=filesync.app 21 | - --static-auth-secret= 22 | ports: 23 | - "3478:3478/tcp" 24 | - "3478:3478/udp" 25 | restart: unless-stopped 26 | 27 | peerjs: 28 | image: peerjs/peerjs-server:1.1.0-rc.2 29 | container_name: filesync-peerjs 30 | command: peerjs --path /peerjs 31 | restart: unless-stopped 32 | 33 | filesync: 34 | image: poliuscorp/filesync 35 | container_name: filesync-app 36 | environment: 37 | - SECRET_KEY= 38 | restart: unless-stopped 39 | 40 | volumes: 41 | filesync: 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Pol Alzina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | # Serve web static files 5 | root /filesync/web; 6 | index index.html; 7 | 8 | # Proxy /api/ requests to FastAPI 9 | location /api/ { 10 | proxy_pass http://127.0.0.1:8000; 11 | proxy_set_header Host $host; 12 | proxy_set_header X-Real-IP $remote_addr; 13 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 14 | proxy_set_header X-Forwarded-Proto $scheme; 15 | } 16 | 17 | # Proxy /peerjs requests to PeerJS server 18 | location /peerjs { 19 | proxy_pass http://peerjs:9000/peerjs; 20 | proxy_http_version 1.1; 21 | proxy_set_header Upgrade $http_upgrade; 22 | proxy_set_header Connection "upgrade"; 23 | proxy_set_header Host $host; 24 | proxy_set_header X-Real-IP $remote_addr; 25 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 26 | proxy_set_header X-Forwarded-Proto $scheme; 27 | } 28 | 29 | # Handle root `/` 30 | location = / { 31 | try_files /index.html =404; 32 | } 33 | 34 | # Catch-all for SPA routes 35 | location / { 36 | try_files $uri $uri/ /index.html; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/js/modules/webrtc/turn.js: -------------------------------------------------------------------------------- 1 | class Turn { 2 | _username = null; 3 | _credential = null; 4 | _expiration = null; 5 | 6 | getServers = async () => { 7 | await this._getCredentials(); 8 | return [ 9 | { 10 | urls: `stun:${window.location.hostname}:3478` 11 | }, 12 | { 13 | urls: `turn:${window.location.hostname}:3478`, 14 | username: this._username, 15 | credential: this._credential, 16 | } 17 | ] 18 | } 19 | 20 | _getCredentials = async () => { 21 | const now = Math.floor(Date.now() / 1000); 22 | 23 | // Check if token is still valid 24 | if (this._expiration !== null && this._expiration > now) { 25 | return { username: this._username, credential: this._credential }; 26 | } 27 | 28 | // Fetch new token 29 | const response = await fetch("/api/credentials", { method: "GET" }); 30 | 31 | if (!response.ok) { 32 | throw new Error("An issue occurred while getting the token."); 33 | } 34 | 35 | // Parse the response as JSON 36 | const data = await response.json(); 37 | 38 | // Get JWT from cookie 39 | const token = data.token 40 | if (!token) throw new Error("Token cookie not found"); 41 | 42 | // Decode JWT to get username and credential 43 | const payload = this._decodeJwt(token); 44 | 45 | // Set cached values 46 | this._username = payload.username; 47 | this._credential = payload.credential; 48 | this._expiration = payload.exp - 10; // Subtract 10 seconds for safety 49 | }; 50 | 51 | _decodeJwt(token) { 52 | const payload = token.split(".")[1]; 53 | const json = atob(payload); 54 | return JSON.parse(json); 55 | } 56 | } 57 | 58 | export const turn = new Turn(); -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | push_to_registry: 9 | name: Push Docker image to Docker Hub 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | id-token: write 15 | attestations: write 16 | 17 | steps: 18 | - name: Check out the repo 19 | uses: actions/checkout@v4 20 | 21 | - name: Log in to Docker Hub 22 | uses: docker/login-action@v3 23 | with: 24 | username: poliuscorp 25 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 26 | 27 | - name: Extract metadata (tags, labels) for Docker 28 | id: meta 29 | uses: docker/metadata-action@v5 30 | with: 31 | images: poliuscorp/filesync 32 | 33 | - name: Set up Docker Buildx 34 | uses: docker/setup-buildx-action@v3 35 | 36 | - name: Set tag without first two chars 37 | id: tag 38 | run: | 39 | SHORT_TAG="${GITHUB_REF_NAME:2}" 40 | echo "SHORT_TAG=$SHORT_TAG" >> $GITHUB_OUTPUT 41 | 42 | - name: Build and push Docker image 43 | id: push 44 | uses: docker/build-push-action@v6 45 | with: 46 | context: . 47 | file: ./Dockerfile 48 | push: true 49 | tags: | 50 | poliuscorp/filesync:latest 51 | poliuscorp/filesync:${{ steps.tag.outputs.short_tag }} 52 | labels: ${{ steps.meta.outputs.labels }} 53 | platforms: linux/amd64,linux/arm64 54 | 55 | - name: Generate artifact attestation 56 | uses: actions/attest-build-provenance@v2 57 | with: 58 | subject-name: index.docker.io/poliuscorp/filesync 59 | subject-digest: ${{ steps.push.outputs.digest }} 60 | push-to-registry: true 61 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # docker buildx build -t filesync:latest --compress --load . 2 | # docker buildx build --platform linux/amd64 -t filesync --compress --output=type=docker,dest=./filesync.tar . 3 | 4 | # ============================== 5 | # Stage 1: Build Python dependencies 6 | # ============================== 7 | FROM python:3.12-alpine AS builder 8 | 9 | # Install build dependencies 10 | RUN apk add --no-cache --virtual .build-deps build-base libffi-dev musl-dev python3-dev 11 | 12 | WORKDIR /filesync 13 | 14 | # Copy requirements 15 | COPY api/requirements.txt . 16 | 17 | # Install Python dependencies to a separate location 18 | RUN pip install --no-cache-dir --prefer-binary --upgrade pip setuptools wheel \ 19 | && pip install --no-cache-dir --prefix=/install -r requirements.txt 20 | 21 | # Optional: remove tests and unnecessary files from site-packages 22 | RUN find /install/lib/python3.12/site-packages -name "tests" -type d -exec rm -rf {} + \ 23 | && find /install/lib/python3.12/site-packages -name "*.pyc" -delete \ 24 | && find /install/lib/python3.12/site-packages -name "*.pyo" -delete \ 25 | && find /install/lib/python3.12/site-packages -name "*.so" -exec strip --strip-unneeded {} + 26 | 27 | # ============================== 28 | # Stage 2: Runtime image 29 | # ============================== 30 | FROM python:3.12-alpine 31 | 32 | # Install Nginx runtime 33 | RUN apk add --no-cache nginx 34 | 35 | WORKDIR /filesync 36 | 37 | # Copy Python dependencies from builder 38 | COPY --from=builder /install /usr/local 39 | 40 | # Copy app code 41 | COPY api /filesync/api 42 | COPY web /filesync/web 43 | 44 | # Copy Nginx config 45 | COPY nginx.conf /etc/nginx/http.d/default.conf 46 | 47 | # Expose ports 48 | EXPOSE 80 49 | 50 | # Start FastAPI + Nginx 51 | CMD ["sh", "-c", "python3 -m uvicorn api.main:app --host 0.0.0.0 --port 8000 & nginx -g 'daemon off;'"] 52 | -------------------------------------------------------------------------------- /api/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hmac 3 | import hashlib 4 | import base64 5 | import time 6 | import jwt 7 | import uuid 8 | from datetime import datetime, timedelta, timezone 9 | from fastapi import FastAPI 10 | from fastapi.middleware.cors import CORSMiddleware 11 | 12 | # Get environment variables 13 | SECRET_KEY = os.getenv("SECRET_KEY") 14 | 15 | # Init FastAPI 16 | app = FastAPI(title='FileSync API', version='1.0', root_path="/api") 17 | 18 | # Allow your dev frontend origin 19 | app.add_middleware( 20 | CORSMiddleware, 21 | allow_origins=["http://127.0.0.1:5500"], 22 | allow_credentials=True, 23 | allow_methods=["*"], 24 | allow_headers=["*"], 25 | ) 26 | 27 | # Add root route 28 | @app.get("/") 29 | async def root(): 30 | return {"message": "Welcome to FileSync API!"} 31 | 32 | # Add health check route 33 | @app.get("/health") 34 | async def health_check(): 35 | return {"message": "FileSync API is running!"} 36 | 37 | # Add uuid route 38 | @app.get("/uuid") 39 | async def uuid_check(): 40 | return {"uuid": str(uuid.uuid4())} 41 | 42 | # Add credentials route 43 | @app.get("/credentials") 44 | async def credentials(): 45 | # Define TTL (5 minutes) 46 | ttl = 300 47 | 48 | # Generate temporary credentials 49 | username, credential = generate_turn_credentials(ttl) 50 | 51 | # Generate token 52 | expiration = datetime.now(tz=timezone.utc) + timedelta(seconds=ttl) 53 | payload = {'username': username, 'credential': credential, 'exp': int(expiration.timestamp())} 54 | token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') 55 | 56 | # Return token 57 | return { "token": token } 58 | 59 | def generate_turn_credentials(ttl): 60 | timestamp = int(time.time()) + ttl 61 | username = f"{timestamp}:{uuid.uuid4().hex}" 62 | dig = hmac.new(SECRET_KEY.encode(), username.encode(), hashlib.sha1).digest() 63 | password = base64.b64encode(dig).decode() 64 | return username, password -------------------------------------------------------------------------------- /web/css/style.css: -------------------------------------------------------------------------------- 1 | :root, 2 | :root.light { 3 | --color-body-bg: #fff; 4 | --color-header: #000; 5 | --color-h1: #000; 6 | --color-h2: #212529; 7 | --color-p: #212529; 8 | --color-input: #000; 9 | --color-input-bg: #fff; 10 | --color-box: #000; 11 | --color-box-bg: #E7E9EB; 12 | --color-url: #0d6efd; 13 | --color-icon: #0f9d58; 14 | --color-button-border: #cdcfd1; 15 | } 16 | :root.dark { 17 | --color-body-bg: #212529; 18 | --color-header: #adb5db; 19 | --color-h1: #fff; 20 | --color-h2: #adb5db; 21 | --color-p: #adb5db; 22 | --color-input: #fff; 23 | --color-input-bg: #212529; 24 | --color-box: #fff; 25 | --color-box-bg: #292e33; 26 | --color-url: #6ea8fe; 27 | --color-icon: #14c48f; 28 | --color-button-border: #464f59; 29 | } 30 | 31 | body { 32 | background-color: var(--color-body-bg); 33 | font-family: Lato,sans-serif; 34 | } 35 | h1 { 36 | color: var(--color-h1); 37 | } 38 | h2 { 39 | color: var(--color-h2); 40 | font-size: 1.6rem; 41 | } 42 | p { 43 | color: var(--color-p); 44 | } 45 | hr { 46 | border-top: 1px solid var(--color-p); 47 | } 48 | input { 49 | box-sizing: border-box; 50 | width: 100%; 51 | border-radius: 5px !important; 52 | font-size: 16px !important; 53 | padding: 10px !important; 54 | color: var(--color-input) !important; 55 | background-color: var(--color-input-bg) !important; 56 | } 57 | input:focus { 58 | outline: none; 59 | border: 1px solid #0d6efd !important; 60 | -webkit-box-shadow: none !important; 61 | -moz-box-shadow: none !important; 62 | box-shadow: none !important; 63 | } 64 | input:not(:focus) { 65 | border: 1px solid rgb(86, 92, 110); 66 | } 67 | textarea { 68 | width: 100%; 69 | padding: 10px; 70 | border: 1px solid rgb(86, 92, 110); 71 | border-radius: 5px; 72 | font-size: 16px; 73 | line-height: 1.5; 74 | color: var(--color-input); 75 | resize: none; 76 | background-color: var(--color-input-bg) 77 | } 78 | textarea:focus { 79 | outline: none; 80 | border-color: #0d6efd; 81 | } 82 | textarea:not(:focus) { 83 | border-color: rgb(86, 92, 110); 84 | } 85 | textarea::-webkit-scrollbar { 86 | width: 8px; 87 | background-color: #f2f2f2; 88 | } 89 | textarea::-webkit-scrollbar-thumb { 90 | background-color: #ccc; 91 | } 92 | .header { 93 | position: fixed; 94 | top: 0; 95 | right: 0; 96 | padding: 10px 15px 15px 15px; 97 | z-index: 2; 98 | background-color: var(--color-body-bg); 99 | color: var(--color-header); 100 | border-bottom-left-radius: 5px; 101 | } 102 | .icon { 103 | padding: 1rem; 104 | color: rgba(255, 255, 255, 1); 105 | background-color: rgb(252, 145, 45); 106 | border-radius: 1rem; 107 | } 108 | .box { 109 | border: 1px solid var(--color-box-bg); 110 | border-radius: 5px; 111 | font-size: 16px; 112 | padding: 10px; 113 | color: var(--color-box); 114 | background-color: var(--color-box-bg); 115 | box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.05); 116 | } 117 | .col-1-3 { 118 | display: none; 119 | } 120 | .col-2-3 { 121 | flex: 0 0 auto; 122 | width: auto; 123 | } 124 | @media (min-width: 1024px) { 125 | .master-div { 126 | margin-left: auto; 127 | margin-right: auto; 128 | width: 1000px; 129 | text-align: center !important; 130 | } 131 | #home { 132 | width:520px; 133 | margin-left:auto; 134 | margin-right:auto; 135 | } 136 | .head { 137 | justify-content: center; 138 | } 139 | .col-1-3 { 140 | display: block; 141 | } 142 | .col-2-3 { 143 | flex: 1 0 0%; 144 | width: 100% 145 | } 146 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | FileSync Logo 3 |

FileSync

4 | 5 | **Send files from one device to many in real-time** 6 | 7 |

8 |  GitHub Release Docker Pulls 9 |

10 | 11 |
12 | 13 |

14 | FileSync is a file sharing web application that allows users to transfer files between multiple devices with end-to-end encryption. 15 |

16 | 17 |
18 | 19 | ![FileSync](web/assets/filesync.png) 20 | 21 |
22 | 23 | ## Install 24 | 25 | **1. Download required files** 26 | 27 | Get the `docker-compose.yml` and `Caddyfile` from the **deploy** folder. 28 | 29 | **2. Generate a secure secret key** 30 | 31 | Run the following command to generate a 32-byte base64-encoded secret: 32 | 33 | ``` 34 | python3 -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())" 35 | ``` 36 | 37 | **3. Configure the secret key** 38 | 39 | Open `docker-compose.yml` and replace **both occurrences** of `` with the generated value from the previous step. 40 | 41 | **Example:** 42 | 43 | ``` 44 | ... 45 | - --static-auth-secret=/RaFOHJQQPAAXRNdaDhfBghvX9+o9UJEazKgIopK3TI= 46 | ... 47 | - SECRET_KEY=/RaFOHJQQPAAXRNdaDhfBghvX9+o9UJEazKgIopK3TI= 48 | ... 49 | ``` 50 | 51 | **4. (Optional) Enable HTTPS** 52 | 53 | To enable HTTPS, edit the `Caddyfile`: 54 | 55 | - Replace `:80` with your domain (e.g., `filesync.app`). 56 | 57 | Caddy will automatically provision and renew SSL certificates for your domain. 58 | 59 | **Example:** 60 | 61 | ``` 62 | filesync.app { 63 | reverse_proxy filesync:80 64 | } 65 | ``` 66 | 67 | **5. Start the services** 68 | 69 | Run the following command to start everything in detached mode: 70 | 71 | ``` 72 | docker-compose up -d 73 | ``` 74 | 75 | **6. Access the application** 76 | 77 | Once the services are up; 78 | 79 | - For local testing, open your browser at: 80 | 81 | ``` 82 | http://localhost 83 | ``` 84 | 85 | - If using a domain, open: 86 | 87 | ``` 88 | https://yourdomain 89 | ``` 90 | 91 | ## Uninstall (Optional) 92 | 93 | To stop and remove the containers, run: 94 | 95 | ``` 96 | docker-compose down 97 | ``` 98 | 99 | ## Under the hood 100 | 101 | FileSync uses [PeerJS](https://github.com/peers/peerjs) (a WebRTC wrapper) to transfer files between multiple devices. Files shared are peer-to-peer, which means there is a direct file transfer between the sender and receiver without any intermediate server. Your files remain private and secure throughout the entire transfer process. 102 | 103 | Do note that a [PeerJS server](https://github.com/peers/peerjs-server) is used to assist in the initial connection setup, ensuring all users can establish peer-to-peer connections effectively. Once the connections are established, the server steps back, allowing the direct transfer of files between the sender and the receiver. At no point during this process does the server have access to the file contents. It solely facilitates the connection between users without compromising the privacy or security of the files being shared. 104 | 105 | ![File Transfer - https://xkcd.com/949](web/assets/comic.png) -------------------------------------------------------------------------------- /web/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test ICE Servers 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Test ICE Servers

12 |
13 |

14 |

🔴 The STUN server is NOT reachable!

15 |

🔴 The TURN server is NOT reachable!

16 |

17 |
18 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /web/js/modules/dom.js: -------------------------------------------------------------------------------- 1 | export const dom = { 2 | // TOP BAR 3 | theme_text: document.getElementById('theme-text'), 4 | about_text: document.getElementById('about-text'), 5 | 6 | // ABOUT 7 | about_div: document.getElementById('about-div'), 8 | comic_img: document.getElementById('comic-img'), 9 | 10 | // ERROR 11 | error_div: document.getElementById('error-div'), 12 | error_message: document.getElementById('error-message'), 13 | 14 | // PASSWORD 15 | password_div: document.getElementById('password-div'), 16 | password_alert: document.getElementById('password-alert'), 17 | password_input: document.getElementById('password-input'), 18 | password_hide: document.getElementById('password-hide'), 19 | password_show: document.getElementById('password-show'), 20 | password_error: document.getElementById('password-error'), 21 | password_submit: document.getElementById('password-submit'), 22 | password_loading: document.getElementById('password-loading'), 23 | 24 | // CONNECT 25 | connect_div: document.getElementById('connect-div'), 26 | 27 | // TRANSFER 28 | transfer_div: document.getElementById('transfer-div'), 29 | 30 | transfer_qr_code: document.getElementById('transfer-qr-code'), 31 | transfer_status_protected: document.getElementById('transfer-status-protected'), 32 | transfer_status_wait: document.getElementById('transfer-status-wait'), 33 | transfer_status_success: document.getElementById('transfer-status-success'), 34 | 35 | transfer_url_value: document.getElementById('transfer-url-value'), 36 | transfer_url_copy: document.getElementById('transfer-url-copy'), 37 | transfer_url_success: document.getElementById('transfer-url-success'), 38 | 39 | transfer_select_file: document.getElementById('transfer-select-file'), 40 | transfer_select_file_input: document.getElementById('transfer-select-file-input'), 41 | transfer_add_password: document.getElementById('transfer-add-password'), 42 | 43 | transfer_users_div: document.getElementById('transfer-users-div'), 44 | transfer_users_count: document.getElementById('transfer-users-count'), 45 | transfer_users_list: document.getElementById('transfer-users-list'), 46 | transfer_users_list_host: document.getElementById('transfer-users-list-host'), 47 | transfer_users_list_host_name: document.getElementById('transfer-users-list-host-name'), 48 | 49 | transfer_files_div: document.getElementById('transfer-files-div'), 50 | transfer_files_count: document.getElementById('transfer-files-count'), 51 | transfer_files_download: document.getElementById('transfer-files-download'), 52 | transfer_files_list: document.getElementById('transfer-files-list'), 53 | transfer_files_list_empty: document.getElementById('transfer-files-list-empty'), 54 | 55 | // PASSWORD MODAL 56 | password_modal: document.getElementById('password-modal'), 57 | password_modal_value: document.getElementById('password-modal-value'), 58 | 59 | // NAME MODAL 60 | name_modal: document.getElementById('name-modal'), 61 | name_modal_value: document.getElementById('name-modal-value'), 62 | name_modal_error: document.getElementById('name-modal-error'), 63 | 64 | // FILE MODAL 65 | file_modal: document.getElementById('file-modal'), 66 | file_modal_table: document.getElementById('file-modal-table'), 67 | file_modal_table_empty: document.getElementById('file-modal-table-empty'), 68 | file_modal_refresh: document.getElementById('file-modal-refresh'), 69 | 70 | // DOWNLOAD MODAL 71 | download_modal: document.getElementById('download-modal'), 72 | download_modal_value: document.getElementById('download-modal-value'), 73 | download_modal_active: document.getElementById('download-modal-active'), 74 | download_modal_success: document.getElementById('download-modal-success'), 75 | download_modal_error: document.getElementById('download-modal-error'), 76 | download_modal_cancel: document.getElementById('download-modal-cancel'), 77 | download_modal_cancel_spinner: document.getElementById('download-modal-cancel-spinner'), 78 | download_modal_close: document.getElementById('download-modal-close'), 79 | 80 | // NOTIFICATION 81 | notification_modal: document.getElementById('notification-modal'), 82 | notification_modal_value: document.getElementById('notification-modal-value'), 83 | } 84 | 85 | // Display a confirmation dialog when the user attempts to refresh or navigate away from the page. 86 | window.addEventListener("beforeunload", (event) => { 87 | event.preventDefault(); 88 | }); 89 | 90 | // Focus the password input after the fade animation 91 | dom.password_modal.addEventListener('shown.bs.modal', () => { 92 | dom.password_modal_value.focus() 93 | }); 94 | 95 | // Focus the name input after the fade animation 96 | dom.name_modal.addEventListener('shown.bs.modal', () => { 97 | dom.name_modal_value.focus() 98 | }); -------------------------------------------------------------------------------- /web/js/modules/script.js: -------------------------------------------------------------------------------- 1 | import { dom } from './dom.js'; 2 | import { User } from './webrtc/user.js'; 3 | 4 | // Room ID 5 | const room_id = window.location.pathname.substring(1) 6 | 7 | // Store current user 8 | var user; 9 | 10 | // QR Code 11 | var qr = new QRious({ 12 | element: document.getElementById('transfer-qr-code'), 13 | background: 'transparent', 14 | size: 220, 15 | foreground: '#adb5db', 16 | level: 'H', 17 | }) 18 | 19 | // Get theme mode 20 | if (window.localStorage.getItem('mode') == 'light') { 21 | dom.theme_text.innerHTML = 'Light' 22 | dom.comic_img.src = "assets/comic.png" 23 | qr.set({foreground: '#212529'}); 24 | } 25 | 26 | // On Load 27 | async function onLoad() { 28 | // Create current user 29 | user = new User(room_id) 30 | 31 | // Host 32 | if (room_id.length == 0) { 33 | // Generate Room ID 34 | const new_room_id = generateRoomID() 35 | 36 | // Init UI Components 37 | dom.transfer_div.style.display = 'block' 38 | dom.transfer_url_value.innerHTML = `${window.location.origin}/${new_room_id}` 39 | dom.transfer_users_list_host_name.innerHTML = user.name + ' (You)' 40 | dom.transfer_users_count.innerHTML = ' (1)' 41 | dom.transfer_add_password.style.display = 'block'; 42 | qr.set({value: dom.transfer_url_value.innerHTML}); 43 | 44 | // Init peer connection 45 | await user.init(new_room_id) 46 | } 47 | // Peer 48 | else { 49 | // Init UI Componente 50 | dom.connect_div.style.display = 'block' 51 | dom.transfer_url_value.innerHTML = `${window.location.origin}/${room_id}` 52 | qr.set({value: dom.transfer_url_value.innerHTML}); 53 | 54 | // Init peer connection 55 | await user.init() 56 | 57 | // Connect to the room 58 | await user.connect(room_id) 59 | } 60 | } 61 | 62 | // Theme 63 | function themeClick() { 64 | if (dom.theme_text.innerHTML == 'Dark') { 65 | dom.theme_text.innerHTML = 'Light' 66 | document.documentElement.classList.remove("dark") 67 | document.documentElement.classList.add("light") 68 | document.documentElement.setAttribute('data-bs-theme', 'light') 69 | window.localStorage.setItem('mode', 'light') 70 | dom.comic_img.src = "assets/comic.png" 71 | qr.set({foreground: '#212529'}); 72 | } 73 | else if (dom.theme_text.innerHTML == 'Light') { 74 | dom.theme_text.innerHTML = 'Dark' 75 | document.documentElement.classList.remove("light") 76 | document.documentElement.classList.add("dark") 77 | document.documentElement.setAttribute('data-bs-theme', 'dark') 78 | window.localStorage.setItem('mode', 'dark') 79 | dom.comic_img.src = "assets/comic-dark.png" 80 | qr.set({foreground: '#adb5db'}); 81 | } 82 | } 83 | window.themeClick = themeClick; 84 | 85 | // About 86 | function aboutClick() { 87 | if (dom.about_text.innerHTML == 'About') { 88 | dom.transfer_div.style.display = 'none' 89 | dom.about_div.style.display = 'block' 90 | dom.about_text.innerHTML = 'Go back' 91 | } 92 | else { 93 | dom.about_div.style.display = 'none' 94 | dom.about_text.innerHTML = 'About' 95 | dom.transfer_div.style.display = 'block' 96 | } 97 | } 98 | window.aboutClick = aboutClick; 99 | 100 | function addPassword() { 101 | const modal = new bootstrap.Modal(dom.password_modal) 102 | modal.show() 103 | } 104 | window.addPassword = addPassword; 105 | 106 | // Show / Hide password 107 | function togglePasswordVisibility(input_name, button_show_name, button_hide_name) { 108 | var password_input = document.getElementById(input_name) 109 | var password_button_show = document.getElementById(button_show_name) 110 | var password_button_hide = document.getElementById(button_hide_name) 111 | if (password_input.type === "password") { 112 | password_input.type = "text" 113 | password_button_show.style.display = 'block' 114 | password_button_hide.style.display = 'none' 115 | } else { 116 | password_input.type = "password" 117 | password_button_show.style.display = 'none' 118 | password_button_hide.style.display = 'block' 119 | } 120 | password_input.focus() 121 | } 122 | window.togglePasswordVisibility = togglePasswordVisibility; 123 | 124 | // Confirm change password 125 | function addPasswordSubmit() { 126 | user.password = dom.password_modal_value.value.trim() 127 | const modal = bootstrap.Modal.getInstance(dom.password_modal); 128 | modal.hide() 129 | dom.transfer_status_protected.style.display = user.password.length == 0 ? 'none' : 'inline-block' 130 | } 131 | window.addPasswordSubmit = addPasswordSubmit; 132 | 133 | function connectWithPassword() { 134 | if (dom.password_input.value.trim().length == 0) { 135 | dom.password_error.style.display = 'block' 136 | } 137 | else { 138 | user.password = dom.password_input.value 139 | dom.password_error.style.display = 'none' 140 | dom.password_submit.setAttribute("disabled", "") 141 | dom.password_loading.style.display = 'inline-block' 142 | user.connect(room_id) 143 | } 144 | } 145 | window.connectWithPassword = connectWithPassword; 146 | 147 | // Change name 148 | function changeName() { 149 | dom.name_modal_value.value = '' 150 | dom.name_modal_error.style.display = 'none' 151 | 152 | const modal = new bootstrap.Modal(dom.name_modal) 153 | modal.show() 154 | } 155 | window.changeName = changeName; 156 | 157 | function changeNameSubmit() { 158 | // Update name 159 | user.changeName(dom.name_modal_value.value.trim()) 160 | } 161 | window.changeNameSubmit = changeNameSubmit; 162 | 163 | // Copy Room url 164 | function copyURL() { 165 | const url = dom.transfer_url_value.innerHTML; 166 | if (navigator.clipboard && window.isSecureContext) { 167 | // Secure context (HTTPS) 168 | navigator.clipboard.writeText(url) 169 | } 170 | else { 171 | // Fallback for HTTP 172 | const textarea = document.createElement("textarea"); 173 | textarea.value = url; 174 | document.body.appendChild(textarea); 175 | textarea.focus(); 176 | textarea.select(); 177 | document.execCommand('copy'); 178 | document.body.removeChild(textarea); 179 | } 180 | 181 | const modal = new bootstrap.Modal(dom.notification_modal) 182 | dom.notification_modal_value.innerHTML = "URL copied." 183 | dom.transfer_url_copy.style.display = 'none' 184 | dom.transfer_url_success.style.display = 'flex' 185 | modal.show() 186 | 187 | setTimeout(() => { 188 | dom.transfer_url_success.style.display = 'none' 189 | dom.transfer_url_copy.style.display = 'flex' 190 | modal.hide() 191 | }, 1000) 192 | } 193 | window.copyURL = copyURL; 194 | 195 | // Send File 196 | function sendFiles(event) { 197 | user.addFiles(event.files) 198 | } 199 | window.sendFiles = sendFiles; 200 | 201 | // Remove file 202 | function removeFile(fileId) { 203 | user.removeFile(fileId) 204 | } 205 | window.removeFile = removeFile; 206 | 207 | // Download File 208 | function downloadFile(id) { 209 | user.downloadFile(id) 210 | } 211 | window.downloadFile = downloadFile; 212 | 213 | // Abort File (Stop file download) 214 | function abortFile(fileId) { 215 | user.abortFile(fileId) 216 | } 217 | window.abortFile = abortFile; 218 | 219 | // See details 220 | function showFileDetails(fileId) { 221 | user.showFileDetails(fileId) 222 | } 223 | window.showFileDetails = showFileDetails; 224 | 225 | // Download all 226 | function downloadAll() { 227 | user.downloadAll() 228 | } 229 | window.downloadAll = downloadAll; 230 | 231 | // Cancel download all 232 | function cancelDownloadAll() { 233 | user.downloadAllCancel() 234 | } 235 | window.cancelDownloadAll = cancelDownloadAll; 236 | 237 | // Function to generate a random string in the format XXX-XXXX-XXX. 238 | function generateRoomID() { 239 | const length = 10 240 | const alphabet = 'abcdefghijklmnopqrstuvwxyz'; 241 | const random = crypto.getRandomValues(new Uint8Array(length)); 242 | let room_id = ""; 243 | for (let i = 0; i < length; i++) { 244 | room_id += alphabet[random[i] % alphabet.length]; 245 | } 246 | return `${room_id.slice(0, 3)}-${room_id.slice(3, 7)}-${room_id.slice(7, 10)}`; 247 | } 248 | 249 | // On document loaded, execute onLoad() method. 250 | (() => onLoad())(); -------------------------------------------------------------------------------- /web/js/modules/webrtc/file.js: -------------------------------------------------------------------------------- 1 | import { dom } from '../dom.js'; 2 | import { turn } from './turn.js'; 3 | 4 | export class File { 5 | // File 6 | _id; 7 | _name; 8 | _size; 9 | _content; 10 | _owner_id; 11 | _owner_name; 12 | 13 | // Send 14 | _remotePeers = {}; 15 | 16 | // Receive 17 | _peer; 18 | _chunks = []; 19 | _transferred = 0; 20 | _zip = false; 21 | _in_progress = false; 22 | _aborted = false; 23 | _removed = false 24 | 25 | constructor(file) { 26 | this._id = file.id 27 | this._name = file.name 28 | this._size = file.size 29 | this._content = file.content 30 | this._owner_id = file.owner_id 31 | this._owner_name = file.owner_name 32 | } 33 | 34 | get file() { 35 | return {"id": this._id, "name": this._name, "size": this._size, "owner_id": this._owner_id, "owner_name": this._owner_name} 36 | } 37 | 38 | get id() { 39 | return this._id; 40 | } 41 | 42 | get name() { 43 | return this._name 44 | } 45 | 46 | get size() { 47 | return this._size 48 | } 49 | 50 | get owner_id() { 51 | return this._owner_id 52 | } 53 | 54 | get owner_name() { 55 | return this._owner_name 56 | } 57 | 58 | get peer() { 59 | return this._peer 60 | } 61 | 62 | get remotePeers() { 63 | return this._remotePeers 64 | } 65 | 66 | get details() { 67 | return Object.values(this._remotePeers).reduce((acc, p) => { 68 | acc[p.user_id] = { 69 | user_name: p.user_name, 70 | progress: p.progress, 71 | aborted: p.aborted, 72 | }; 73 | return acc; 74 | }, {}); 75 | } 76 | 77 | get in_progress() { 78 | return this._in_progress 79 | } 80 | 81 | set in_progress(value) { 82 | this._in_progress = value 83 | } 84 | 85 | get aborted() { 86 | return this._aborted 87 | } 88 | 89 | get removed() { 90 | return this._removed 91 | } 92 | 93 | set removed(value) { 94 | return this._removed = value 95 | } 96 | 97 | get transferred() { 98 | return this._transferred 99 | } 100 | 101 | get chunks() { 102 | return this._chunks 103 | } 104 | 105 | set chunks(value) { 106 | this._chunks = value 107 | } 108 | 109 | set owner_name(value) { 110 | this._owner_name = value 111 | } 112 | 113 | set zip(value) { 114 | this._zip = value 115 | } 116 | 117 | async init(peer_id) { 118 | // Get ICE servers 119 | let iceServers; 120 | try { 121 | iceServers = await turn.getServers(); 122 | } catch (err) { 123 | console.log(err) 124 | dom.transfer_div.style.display = 'none' 125 | dom.connect_div.style.display = 'none' 126 | dom.error_div.style.display = 'block' 127 | dom.error_message.innerHTML = 'An error occurred getting the ICE servers. Please try again later.' 128 | return 129 | } 130 | 131 | await new Promise(async (resolve) => { 132 | // Get UUID 133 | const uuid = await this._getUUID(); 134 | 135 | // Create a new Peer instance 136 | const isSecure = window.isSecureContext && window.location.hostname !== 'localhost'; 137 | const peer = new Peer(uuid, { 138 | host: window.location.hostname, 139 | port: isSecure ? 443 : 80, 140 | path: "/peerjs", 141 | secure: isSecure, 142 | config: { 143 | iceServers: iceServers, 144 | // iceTransportPolicy: "relay", // Force TURN 145 | } 146 | }); 147 | 148 | // Emitted when a connection to the PeerServer is established. 149 | peer.on('open', (id) => this._handleOpen(id, resolve)); 150 | 151 | // Emitted when a new data connection is established from a remote peer. 152 | peer.on('connection', (conn) => conn.on('open', () => this._handleConnection(conn))); 153 | 154 | // Errors on the peer are almost always fatal and will destroy the peer. 155 | peer.on('error', (err) => this._handleError(err)); 156 | 157 | // Store peer (Receiver) 158 | if (peer_id === undefined) this._peer = peer 159 | // Store peer (Sender) 160 | else this._remotePeers[peer_id].peer = peer 161 | }) 162 | } 163 | 164 | async connect(peer_id) { 165 | // Init new peer 166 | await this.init(peer_id) 167 | 168 | // Establish a connection with the target peer. 169 | await new Promise((resolve) => { 170 | const conn = this._remotePeers[peer_id].peer.connect(peer_id); 171 | 172 | // Emitted when the connection is established and ready-to-use. 173 | conn.on('open', () => this._handleConnection(conn, resolve)); 174 | }) 175 | } 176 | 177 | async transfer(data) { 178 | // Update UI 179 | document.getElementById(`file-${this._id}-error`).style.display = 'none' 180 | document.getElementById(`file-${this._id}-icon-success`).style.display = 'none' 181 | document.getElementById(`file-${this._id}-icon-loading`).style.display = 'block' 182 | document.getElementById(`file-${this._id}-progress`).innerHTML = '0% | ' 183 | 184 | // Store peer data 185 | this._remotePeers[data.peer_id] = {"user_id": data.requester_id, "user_name": data.requester_name, "peer": null, "conn": null, "online": true, "interval": null, "progress": 0, "aborted": false} 186 | 187 | // Connect to peer_id 188 | await this.connect(data.peer_id) 189 | 190 | // Get connection 191 | const conn = this._remotePeers[data.peer_id].conn 192 | 193 | // Init interval to check connection status 194 | this._remotePeers[data.peer_id].interval = setInterval(() => this._isAlive(conn.peer), 500) 195 | 196 | // Define vars for chunk processing 197 | const chunkSize = 1024 * 1024 // 1 MB 198 | let offset = 0 199 | let position = 0 200 | 201 | // Recursive function to process each chunk 202 | const processChunk = async () => { 203 | // Get the current chunk based on the offset and chunk size 204 | const chunk = this._content.slice(offset, offset + chunkSize) 205 | const isLastChunk = offset + chunkSize >= this._size 206 | 207 | // Check if the connection to the remote peer is open 208 | if (conn.peerConnection != null && conn.peerConnection.iceConnectionState != 'disconnected') { 209 | // const encryptedChunk = await this._encrypt(chunk, `PASSWORD_VALUE`) 210 | conn.send({"webrtc-file-transfer": {"file": chunk, "transferred": chunk.size, "position": position}}) 211 | } 212 | 213 | // If it's not the last chunk, process the next one 214 | if (data.peer_id in this._remotePeers && !isLastChunk) { 215 | offset += chunkSize 216 | position += 1 217 | await processChunk() 218 | } 219 | } 220 | // Start processing the chunks 221 | processChunk() 222 | } 223 | 224 | abort() { 225 | this._aborted = true 226 | } 227 | 228 | remove() { 229 | this._aborted = true 230 | this._removed = true 231 | } 232 | 233 | // Emitted when a connection to the PeerServer is established. 234 | _handleOpen(id, resolve) { 235 | // console.log('My file peer ID is', id) 236 | resolve() 237 | } 238 | 239 | // Emitted when the connection is established and ready-to-use. 240 | _handleConnection(conn, resolve) { 241 | // console.log('Received file connection from', conn.peer) 242 | 243 | // Emitted when data is received from the remote peer. 244 | conn.on('data', (data) => this._handleData(conn, data)); 245 | 246 | // Emitted when either you or the remote peer closes the data connection. 247 | conn.on('close', () => this._handleClose(conn)); 248 | 249 | // Emitted when there is an unexpected error in the data connection. 250 | conn.on('error', (err) => this._handleError(err)); 251 | 252 | // Sender 253 | if (resolve !== undefined) { 254 | this._remotePeers[conn.peer].conn = conn 255 | resolve() 256 | } 257 | // Receiver 258 | else { 259 | this._chunks = [] 260 | this._transferred = 0 261 | this._aborted = false 262 | } 263 | } 264 | 265 | // Check if file connection is alive. 266 | async _isAlive(peer_id) { 267 | if (this._remotePeers[peer_id].conn.peerConnection === null || this._remotePeers[peer_id].conn.peerConnection.iceConnectionState == 'disconnected') { 268 | clearInterval(this._remotePeers[peer_id].interval); 269 | if (this._remotePeers[peer_id].progress != 100) { 270 | this._remotePeers[peer_id].aborted = true 271 | } 272 | this._onFileProgress() 273 | this._remotePeers[peer_id].online = false 274 | } 275 | } 276 | 277 | // Emitted when data is received from the remote peer. 278 | async _handleData(conn, data) { 279 | if ("webrtc-file-transfer" in data) { 280 | this._onFileTransfer(conn, data['webrtc-file-transfer']) 281 | } 282 | else if ("webrtc-file-progress" in data) { 283 | this._onFileProgress(conn, data['webrtc-file-progress']) 284 | } 285 | else if ("webrtc-file-abort" in data) { 286 | this._onFileAborted(conn) 287 | } 288 | } 289 | 290 | async _onFileTransfer(conn, data) { 291 | // If it's aborted, do nothing. 292 | if (this._aborted) { 293 | conn.send({"webrtc-file-abort": true}) 294 | conn.close() 295 | this._peer.destroy() 296 | this._in_progress = false 297 | return 298 | } 299 | 300 | // Store file chunk 301 | // const decryptedChunk = await this._decrypt(data.file, `PASSWORD_VALUE`) 302 | this._chunks[data.position] = data.file 303 | this._transferred += data.transferred 304 | 305 | // Compute progress 306 | this._progress = Math.floor(this._transferred / this._size * 100) 307 | 308 | // Update Progress UI 309 | if (!this._zip) { 310 | document.getElementById(`file-${this._id}-progress`).innerHTML = `${this._progress}% | ` 311 | } 312 | 313 | // Notify progress 314 | conn.send({"webrtc-file-progress": {"progress": this._progress}}) 315 | 316 | // If it's the last chunk 317 | if (this._transferred == this._size) { 318 | if (!this._zip) { 319 | // Update UI 320 | document.getElementById(`file-${this._id}-download`).style.display = 'block' 321 | document.getElementById(`file-${this._id}-abort`).style.display = 'none' 322 | document.getElementById(`file-${this._id}-icon-loading`).style.display = 'none' 323 | document.getElementById(`file-${this._id}-icon-success`).style.display = 'block' 324 | 325 | // Create a new Blob from all file chunks 326 | const blob = new Blob(this._chunks); 327 | 328 | // Download file 329 | const downloadLink = document.createElement('a') 330 | downloadLink.href = URL.createObjectURL(blob) 331 | downloadLink.download = this._name 332 | downloadLink.click() 333 | 334 | // Clean data 335 | this._chunks = [] 336 | this._transferred = 0 337 | } 338 | 339 | // Update internal parameters 340 | this._in_progress = false 341 | 342 | // Close connection 343 | conn.close() 344 | 345 | // Destroy current peer to free up resources 346 | this._peer.destroy() 347 | } 348 | } 349 | 350 | _onFileProgress(conn, data) { 351 | // Track data transfer progress 352 | if (data) { 353 | this._remotePeers[conn.peer].progress = data.progress 354 | } 355 | 356 | // Calculate overall progress 357 | const onlinePeers = Object.values(this._remotePeers).filter(x => x.online) 358 | const totalProgress = onlinePeers.reduce((sum, x) => sum + x.progress, 0) 359 | const overall_progress = onlinePeers.length == 0 ? 0 : Math.floor(totalProgress / onlinePeers.length) 360 | 361 | // Update UI: Show the overall progress 362 | document.getElementById(`file-${this._id}-progress`).innerHTML = `${overall_progress}% | ` 363 | 364 | if (overall_progress == 100) { 365 | document.getElementById(`file-${this._id}-abort`).style.display = 'none' 366 | document.getElementById(`file-${this._id}-icon-loading`).style.display = 'none' 367 | document.getElementById(`file-${this._id}-icon-success`).style.display = 'block' 368 | } 369 | else if (!this._aborted && onlinePeers.filter(x => !x.aborted).length == 0) { 370 | document.getElementById(`file-${this._id}-icon-loading`).style.display = 'none' 371 | document.getElementById(`file-${this._id}-error`).style.display = 'block' 372 | document.getElementById(`file-${this._id}-error`).innerHTML = 'All users stopped the file transfer.' 373 | } 374 | } 375 | 376 | _onFileAborted(conn) { 377 | this._remotePeers[conn.peer].aborted = true 378 | this._onFileProgress() 379 | } 380 | 381 | // Emitted when either you or the remote peer closes the data connection. 382 | _handleClose(conn) { 383 | // Sender 384 | if (conn.peer in this._remotePeers) { 385 | this._remotePeers[conn.peer].peer.destroy() 386 | } 387 | } 388 | 389 | // Emitted when there is an unexpected error in the data connection. 390 | _handleError(err) { 391 | console.error("An error occurred.", err) 392 | } 393 | 394 | // Function to encrypt a chunk of data with AES-GCM 395 | async _encrypt(chunk, key) { 396 | // Step 1: Encode the key 397 | const encoder = new TextEncoder(); 398 | const keyData = encoder.encode(key); 399 | 400 | // Step 2: Generate the IV 401 | const ivBuffer = await crypto.subtle.digest('SHA-256', keyData); 402 | const iv = new Uint8Array(ivBuffer.slice(0, 12)); 403 | 404 | // Step 3: Import the key data as a CryptoKey object 405 | const importedKey = await crypto.subtle.importKey('raw', keyData, { name: 'PBKDF2' }, false, ['deriveKey']); 406 | 407 | // Step 4: Derive a fixed-size key using a Key Derivation Function (KDF) 408 | const derivedKey = await crypto.subtle.deriveKey({name: 'PBKDF2', salt: new Uint8Array(ivBuffer), iterations: 100000, hash: 'SHA-256'}, importedKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']); 409 | 410 | // Step 5: Convert the Blob chunk to an ArrayBuffer 411 | const arrayBuffer = await chunk.arrayBuffer(); 412 | 413 | // Step 6: Encrypt the chunk with AES-GCM algorithm 414 | const encryptedData = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, derivedKey, arrayBuffer); 415 | 416 | // Step 7: Return the encrypted data as ArrayBuffer 417 | return encryptedData; 418 | } 419 | 420 | // Function to decrypt a chunk of encrypted data with AES-GCM 421 | async _decrypt(chunk, key) { 422 | // Step 1: Encode the key 423 | const encoder = new TextEncoder(); 424 | const keyData = encoder.encode(key); 425 | 426 | // Step 2: Extract the IV and encrypted data 427 | const ivBuffer = await crypto.subtle.digest('SHA-256', keyData); 428 | const iv = new Uint8Array(ivBuffer.slice(0, 12)); 429 | 430 | // Step 3: Import the key data as a CryptoKey object 431 | const importedKey = await crypto.subtle.importKey('raw', keyData, { name: 'PBKDF2' }, false, ['deriveKey']); 432 | 433 | // Step 4: Derive a fixed-size key using a Key Derivation Function (KDF) 434 | const derivedKey = await crypto.subtle.deriveKey({name: 'PBKDF2', salt: new Uint8Array(ivBuffer), iterations: 100000, hash: 'SHA-256'}, importedKey, { name: 'AES-GCM', length: 256 }, false, ['decrypt']); 435 | 436 | // Step 5: Decrypt the encrypted data with AES-GCM algorithm 437 | const decryptedData = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, derivedKey, chunk); 438 | 439 | // Step 6: Convert the decrypted ArrayBuffer to a Blob 440 | const decryptedBlob = new Blob([decryptedData]); 441 | 442 | // Step 7: Return the decrypted data as Blob 443 | return decryptedBlob; 444 | } 445 | 446 | async _getUUID() { 447 | const response = await fetch(`/api/uuid`); 448 | const data = await response.json(); 449 | return data['uuid']; 450 | } 451 | } -------------------------------------------------------------------------------- /web/js/vendors/qrious.min.js: -------------------------------------------------------------------------------- 1 | /*! QRious v4.0.2 | (C) 2017 Alasdair Mercer | GPL v3 License 2 | Based on jsqrencode | (C) 2010 tz@execpc.com | GPL v3 License 3 | */ 4 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.QRious=e()}(this,function(){"use strict";function t(t,e){var n;return"function"==typeof Object.create?n=Object.create(t):(s.prototype=t,n=new s,s.prototype=null),e&&i(!0,n,e),n}function e(e,n,s,r){var o=this;return"string"!=typeof e&&(r=s,s=n,n=e,e=null),"function"!=typeof n&&(r=s,s=n,n=function(){return o.apply(this,arguments)}),i(!1,n,o,r),n.prototype=t(o.prototype,s),n.prototype.constructor=n,n.class_=e||o.class_,n.super_=o,n}function i(t,e,i){for(var n,s,a=0,h=(i=o.call(arguments,2)).length;a>1&1,n=0;n0;e--)n[e]=n[e]?n[e-1]^_.EXPONENT[v._modN(_.LOG[n[e]]+t)]:n[e-1];n[0]=_.EXPONENT[v._modN(_.LOG[n[0]]+t)]}for(t=0;t<=i;t++)n[t]=_.LOG[n[t]]},_checkBadness:function(){var t,e,i,n,s,r=0,o=this._badness,a=this.buffer,h=this.width;for(s=0;sh*h;)u-=h*h,c++;for(r+=c*v.N4,n=0;n=o-2&&(t=o-2,s>9&&t--);var a=t;if(s>9){for(r[a+2]=0,r[a+3]=0;a--;)e=r[a],r[a+3]|=255&e<<4,r[a+2]=e>>4;r[2]|=255&t<<4,r[1]=t>>4,r[0]=64|t>>12}else{for(r[a+1]=0,r[a+2]=0;a--;)e=r[a],r[a+2]|=255&e<<4,r[a+1]=e>>4;r[1]|=255&t<<4,r[0]=64|t>>4}for(a=t+3-(s<10);a=5&&(i+=v.N1+n[e]-5);for(e=3;et||3*n[e-3]>=4*n[e]||3*n[e+3]>=4*n[e])&&(i+=v.N3);return i},_finish:function(){this._stringBuffer=this.buffer.slice();var t,e,i=0,n=3e4;for(e=0;e<8&&(this._applyMask(e),(t=this._checkBadness())>=1)1&n&&(s[r-1-e+8*r]=1,e<6?s[8+r*e]=1:s[8+r*(e+1)]=1);for(e=0;e<7;e++,n>>=1)1&n&&(s[8+r*(r-7+e)]=1,e?s[6-e+8*r]=1:s[7+8*r]=1)},_interleaveBlocks:function(){var t,e,i=this._dataBlock,n=this._ecc,s=this._eccBlock,r=0,o=this._calculateMaxLength(),a=this._neccBlock1,h=this._neccBlock2,f=this._stringBuffer;for(t=0;t1)for(t=u.BLOCK[n],i=s-7;;){for(e=s-7;e>t-3&&(this._addAlignment(e,i),!(e6)for(t=d.BLOCK[r-7],e=17,i=0;i<6;i++)for(n=0;n<3;n++,e--)1&(e>11?r>>e-12:t>>e)?(s[5-i+o*(2-n+o-11)]=1,s[2-n+o-11+o*(5-i)]=1):(this._setMask(5-i,2-n+o-11),this._setMask(2-n+o-11,5-i))},_isMasked:function(t,e){var i=v._getMaskBit(t,e);return 1===this._mask[i]},_pack:function(){var t,e,i,n=1,s=1,r=this.width,o=r-1,a=r-1,h=(this._dataBlock+this._eccBlock)*(this._neccBlock1+this._neccBlock2)+this._neccBlock2;for(e=0;ee&&(i=t,t=e,e=i),i=e,i+=e*e,i>>=1,i+=t},_modN:function(t){for(;t>=255;)t=((t-=255)>>8)+(255&t);return t},N1:3,N2:3,N3:40,N4:10}),p=v,m=f.extend({draw:function(){this.element.src=this.qrious.toDataURL()},reset:function(){this.element.src=""},resize:function(){var t=this.element;t.width=t.height=this.qrious.size}}),g=h.extend(function(t,e,i,n){this.name=t,this.modifiable=Boolean(e),this.defaultValue=i,this._valueTransformer=n},{transform:function(t){var e=this._valueTransformer;return"function"==typeof e?e(t,this):t}}),k=h.extend(null,{abs:function(t){return null!=t?Math.abs(t):null},hasOwn:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},noop:function(){},toUpperCase:function(t){return null!=t?t.toUpperCase():null}}),w=h.extend(function(t){this.options={},t.forEach(function(t){this.options[t.name]=t},this)},{exists:function(t){return null!=this.options[t]},get:function(t,e){return w._get(this.options[t],e)},getAll:function(t){var e,i=this.options,n={};for(e in i)k.hasOwn(i,e)&&(n[e]=w._get(i[e],t));return n},init:function(t,e,i){"function"!=typeof i&&(i=k.noop);var n,s;for(n in this.options)k.hasOwn(this.options,n)&&(s=this.options[n],w._set(s,s.defaultValue,e),w._createAccessor(s,e,i));this._setAll(t,e,!0)},set:function(t,e,i){return this._set(t,e,i)},setAll:function(t,e){return this._setAll(t,e)},_set:function(t,e,i,n){var s=this.options[t];if(!s)throw new Error("Invalid option: "+t);if(!s.modifiable&&!n)throw new Error("Option cannot be modified: "+t);return w._set(s,e,i)},_setAll:function(t,e,i){if(!t)return!1;var n,s=!1;for(n in t)k.hasOwn(t,n)&&this._set(n,t[n],e,i)&&(s=!0);return s}},{_createAccessor:function(t,e,i){var n={get:function(){return w._get(t,e)}};t.modifiable&&(n.set=function(n){w._set(t,n,e)&&i(n,t)}),Object.defineProperty(e,t.name,n)},_get:function(t,e){return e["_"+t.name]},_set:function(t,e,i){var n="_"+t.name,s=i[n],r=t.transform(null!=e?e:t.defaultValue);return i[n]=r,r!==s}}),M=w,b=h.extend(function(){this._services={}},{getService:function(t){var e=this._services[t];if(!e)throw new Error("Service is not being managed with name: "+t);return e},setService:function(t,e){if(this._services[t])throw new Error("Service is already managed with name: "+t);e&&(this._services[t]=e)}}),B=new M([new g("background",!0,"white"),new g("backgroundAlpha",!0,1,k.abs),new g("element"),new g("foreground",!0,"black"),new g("foregroundAlpha",!0,1,k.abs),new g("level",!0,"L",k.toUpperCase),new g("mime",!0,"image/png"),new g("padding",!0,null,k.abs),new g("size",!0,100,k.abs),new g("value",!0,"")]),y=new b,O=h.extend(function(t){B.init(t,this,this.update.bind(this));var e=B.get("element",this),i=y.getService("element"),n=e&&i.isCanvas(e)?e:i.createCanvas(),s=e&&i.isImage(e)?e:i.createImage();this._canvasRenderer=new c(this,n,!0),this._imageRenderer=new m(this,s,s===e),this.update()},{get:function(){return B.getAll(this)},set:function(t){B.setAll(t,this)&&this.update()},toDataURL:function(t){return this.canvas.toDataURL(t||this.mime)},update:function(){var t=new p({level:this.level,value:this.value});this._canvasRenderer.render(t),this._imageRenderer.render(t)}},{use:function(t){y.setService(t.getName(),t)}});Object.defineProperties(O.prototype,{canvas:{get:function(){return this._canvasRenderer.getElement()}},image:{get:function(){return this._imageRenderer.getElement()}}});var A=O,L=h.extend({getName:function(){}}).extend({createCanvas:function(){},createImage:function(){},getName:function(){return"element"},isCanvas:function(t){},isImage:function(t){}}).extend({createCanvas:function(){return document.createElement("canvas")},createImage:function(){return document.createElement("img")},isCanvas:function(t){return t instanceof HTMLCanvasElement},isImage:function(t){return t instanceof HTMLImageElement}});return A.use(new L),A}); 5 | 6 | //# sourceMappingURL=qrious.min.js.map -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FileSync.app | Send files securely in real-time. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 25 | 26 | 27 |
28 | Dark 29 | | 30 | About 31 |
32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 |
46 |

FileSync

47 |
48 |
49 |

Send files from one device to many in real-time.

50 | 51 | 52 | 70 | 71 | 72 | 78 | 79 | 80 | 84 | 85 | 86 | 121 | 122 | 123 | 240 | 241 | 242 | 280 | 281 | 282 | 312 | 313 | 314 | 353 | 354 | 355 | 399 | 400 | 401 | 411 | 412 | 413 | 416 |
417 |
418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /web/js/modules/webrtc/user.js: -------------------------------------------------------------------------------- 1 | import { dom } from '../dom.js'; 2 | import { turn } from './turn.js'; 3 | import { File } from './file.js'; 4 | 5 | export class User { 6 | _name = this._generate_name(); 7 | _password = ''; 8 | _peer = null; 9 | _remotePeers = {}; 10 | _room_id; 11 | _isHost; 12 | _files = {}; 13 | _status; 14 | _downloadAll; 15 | 16 | constructor(room_id) { 17 | this._room_id = room_id; 18 | this._isHost = room_id.length == 0; 19 | } 20 | 21 | get id() { 22 | return this._peer.id 23 | } 24 | 25 | get name() { 26 | return this._name 27 | } 28 | 29 | get password() { 30 | return this._password 31 | } 32 | 33 | get isHost() { 34 | return this._isHost 35 | } 36 | 37 | get files() { 38 | return this._files 39 | } 40 | 41 | set password(value) { 42 | this._password = value 43 | } 44 | 45 | async init(peer_id = null) { 46 | // Get UUID 47 | if (peer_id == null) peer_id = await this._getUUID(); 48 | 49 | // Get ICE servers 50 | let iceServers; 51 | try { 52 | iceServers = await turn.getServers(); 53 | } catch (err) { 54 | console.log(err) 55 | dom.transfer_div.style.display = 'none' 56 | dom.connect_div.style.display = 'none' 57 | dom.error_div.style.display = 'block' 58 | dom.error_message.innerHTML = 'An error occurred getting the ICE servers. Please try again later.' 59 | return 60 | } 61 | 62 | await new Promise((resolve) => { 63 | // Create a new Peer instance 64 | const isSecure = window.isSecureContext && window.location.hostname !== 'localhost'; 65 | this._peer = new Peer(peer_id, { 66 | host: window.location.hostname, 67 | port: isSecure ? 443 : 80, 68 | path: "/peerjs", 69 | secure: isSecure, 70 | config: { 71 | iceServers: iceServers, 72 | // iceTransportPolicy: "relay", // Force TURN 73 | } 74 | }); 75 | 76 | // Emitted when a connection to the PeerServer is established. 77 | this._peer.on('open', () => this._handleOpen(resolve)); 78 | 79 | // Emitted when a new data connection is established from a remote peer. 80 | this._peer.on('connection', (conn) => conn.on('open', () => this._handleConnection(conn))); 81 | 82 | // Errors on the peer are almost always fatal and will destroy the peer. 83 | this._peer.on('error', (err) => this._handleError(err)); 84 | }) 85 | } 86 | 87 | async connect(peer_id) { 88 | await new Promise((resolve) => { 89 | // Establish a connection with the host. 90 | const conn = this._peer.connect(peer_id); 91 | 92 | // Emitted when the peer connection has established connection to the host. 93 | conn.on('open', () => this._handleConnection(conn, resolve)); 94 | }) 95 | } 96 | 97 | _isAlive(peer_id) { 98 | if (this._remotePeers[peer_id] === undefined || this._remotePeers[peer_id].conn.peerConnection === null || this._remotePeers[peer_id].conn.peerConnection.iceConnectionState == 'disconnected') { 99 | clearInterval(this._remotePeers[peer_id].interval); 100 | this._handleClose(this._remotePeers[peer_id].conn); 101 | } 102 | } 103 | 104 | // Emitted when a connection to the PeerServer is established. 105 | _handleOpen(resolve) { 106 | resolve() 107 | } 108 | 109 | // Change user's name 110 | changeName(value) { 111 | // Check if there is another user with the same name 112 | const duplicated = Object.entries(this._remotePeers).some(([k, v]) => v.name == value && k != this._peer.id) 113 | if (duplicated) { 114 | dom.name_modal_error.style.display = 'block' 115 | return 116 | } 117 | 118 | // Update user 119 | this._name = value 120 | 121 | // Update files 122 | for (let f of Object.values(this._files)) { 123 | if (f.owner_id == this._peer.id) { 124 | f.owner_name = this._name 125 | document.getElementById(`file-${f.id}-info`).innerHTML = `${this._parseBytes(f.size)} | Sent by ${f.owner_id == this._peer.id ? `${f.owner_name} (You)` : f.owner_name }` 126 | } 127 | } 128 | 129 | if (this._isHost) { 130 | // Update UI 131 | document.getElementById('transfer-users-list-host-name').innerHTML = `${this._name} (You)` 132 | 133 | // Notify all peers 134 | const peers_list = [{"id": this._peer.id, "name": this._name }, ...Object.entries(this._remotePeers).map(([k, v]) => ({"id": k, "name": v.name}))]; 135 | for (let p of Object.values(this._remotePeers)) { 136 | p.conn.send({'webrtc-peers': peers_list}) 137 | } 138 | } 139 | else { 140 | // Update UI 141 | document.getElementById(`user-${this._peer.id}-name`).innerHTML = `${this._name} (You)` 142 | 143 | // Notify Host 144 | this._remotePeers[this._room_id].conn.send({'webrtc-user-name': {"id": this._peer.id, "name": this._name}}) 145 | } 146 | 147 | // Close modal 148 | const modal = bootstrap.Modal.getInstance(dom.name_modal); 149 | modal.hide() 150 | } 151 | 152 | // Add one or multiple file to be transferred to all peers 153 | async addFiles(files) { 154 | let data = [] 155 | for (const file of files) { 156 | // Parse file 157 | const fileData = { 158 | "id": await this._getUUID(), 159 | "name": file.name, 160 | "size": file.size, 161 | "content": file, 162 | "owner_id": this._peer.id, 163 | "owner_name": this._name, 164 | }; 165 | 166 | // Create file instance 167 | const f = new File(fileData); 168 | 169 | // Add file to the current user 170 | this._files[f.id] = f 171 | 172 | // Add file to the list 173 | this._addFileUI(f) 174 | 175 | // Store file to be send to other peers 176 | data.push({"id": f.id, "name": f.name, "size": f.size, "owner_id": f.owner_id, "owner_name": f.owner_name}) 177 | } 178 | 179 | // Send file to all remote peers (If host, then all peers. If peer, then to the host) 180 | for (let peer of Object.values(this._remotePeers)) { 181 | if ('conn' in peer) peer.conn.send({"webrtc-file-add": data}) 182 | } 183 | } 184 | 185 | // Remove a file shared by you 186 | removeFile(fileId) { 187 | this._files[fileId]._aborted = true 188 | 189 | // Notify all peers 190 | for (let peer of Object.values(this._remotePeers)) { 191 | if ('conn' in peer) peer.conn.send({'webrtc-file-remove': {"peer_id": this._peer.id, "file_id": fileId}}) 192 | } 193 | // Update UI 194 | document.getElementById(`file-${fileId}-remove`).style.display = 'none' 195 | document.getElementById(`file-${fileId}-icon-loading`).style.display = 'none' 196 | document.getElementById(`file-${fileId}-icon-success`).style.display = 'none' 197 | document.getElementById(`file-${fileId}-icon-failed`).style.display = 'none' 198 | document.getElementById(`file-${fileId}-error`).style.display = 'block' 199 | document.getElementById(`file-${fileId}-error`).innerHTML = 'You have removed this file.' 200 | } 201 | 202 | // Download a file shared by another peer 203 | async downloadFile(fileId) { 204 | // Update UI: Remove Download button and add loading icon 205 | document.getElementById(`file-${fileId}-download`).style.display = 'none' 206 | document.getElementById(`file-${fileId}-error`).innerHTML = '' 207 | document.getElementById(`file-${fileId}-error`).style.display = 'none' 208 | document.getElementById(`file-${fileId}-abort`).style.display = 'block' 209 | document.getElementById(`file-${fileId}-icon-success`).style.display = 'none' 210 | document.getElementById(`file-${fileId}-icon-failed`).style.display = 'none' 211 | document.getElementById(`file-${fileId}-icon-loading`).style.display = 'block' 212 | document.getElementById(`file-${fileId}-progress`).innerHTML = '0% | ' 213 | 214 | // Init Peering connection to receive the file 215 | await this._files[fileId].init() 216 | 217 | // If it's the host redirect the request to the Origin's Peer. Otherwise send the request to the Host. 218 | this._remotePeers[this._isHost ? this._files[fileId].owner_id : this._room_id].conn.send({'webrtc-file-download': {"file_id": fileId, "requester_id": this._peer.id, "requester_name": this._name, "peer_id": this._files[fileId].peer.id}}) 219 | } 220 | 221 | // Abort a file that is already being downloaded 222 | abortFile(fileId) { 223 | // Abort the file transfer 224 | this._files[fileId].abort() 225 | 226 | // Update UI 227 | document.getElementById(`file-${fileId}-abort`).style.display = 'none' 228 | document.getElementById(`file-${fileId}-download`).style.display = 'block' 229 | document.getElementById(`file-${fileId}-icon-loading`).style.display = 'none' 230 | document.getElementById(`file-${fileId}-icon-failed`).style.display = 'none' 231 | document.getElementById(`file-${fileId}-error`).style.display = 'block' 232 | document.getElementById(`file-${fileId}-error`).innerHTML = 'You have stopped the file transfer.' 233 | } 234 | 235 | // See file details 236 | showFileDetails(fileId) { 237 | // Compute data 238 | this.getFileDetails(fileId); 239 | 240 | // Show modal 241 | const modal = new bootstrap.Modal(dom.file_modal) 242 | modal.show() 243 | } 244 | 245 | getFileDetails(fileId) { 246 | // Hydrate details with the user's online status. 247 | let details = Object.entries(this._files[fileId].details).reduce((acc, [k, v]) => { 248 | acc[k] = {...v, online: k in this._remotePeers}; 249 | return acc; 250 | }, {}); 251 | 252 | // Set Refresh button handler 253 | dom.file_modal_refresh.onclick = () => { 254 | this.getFileDetails(fileId) 255 | }; 256 | 257 | // Update UI 258 | dom.file_modal_table_empty.style.display = Object.values(details).length == 0 ? 'block' : 'none' 259 | dom.file_modal_table.style.display = Object.values(details).length == 0 ? 'none' : 'table' 260 | 261 | // Build table 262 | dom.file_modal_table.querySelector('tbody').innerHTML = '' 263 | for (let user of Object.values(details)) { 264 | dom.file_modal_table.querySelector('tbody').innerHTML += ` 265 | 266 | ${user.user_name} 267 | ${user.progress}% 268 | ${user.progress == 100 ? 'Completed' : user.aborted ? 'Stopped' : 'In progress'} 269 | 270 | 271 | 272 | 273 | 274 | 275 | ` 276 | } 277 | } 278 | 279 | async downloadAll() { 280 | // Init UI Components 281 | dom.download_modal_value.innerHTML = '0%' 282 | dom.download_modal_active.querySelector('.progress-bar').style.width = '0%' 283 | dom.download_modal_active.style.display = 'flex' 284 | dom.download_modal_success.style.display = 'none' 285 | dom.download_modal_error.style.display = 'none' 286 | dom.download_modal_close.style.display = 'none' 287 | dom.download_modal_cancel.style.display = 'block' 288 | dom.download_modal_cancel.removeAttribute("disabled") 289 | dom.download_modal_cancel_spinner.style.display = 'none' 290 | 291 | // Get available files to download 292 | const files = Object.values(this._files).filter(x => x.owner_id != this._peer.id && !x.removed) 293 | 294 | // Check if at least there is a file to be downloaded 295 | if (files.length == 0) { 296 | const modal = new bootstrap.Modal(dom.notification_modal) 297 | dom.notification_modal_value.innerHTML = "There are no files to be downloaded." 298 | modal.show() 299 | setTimeout(() => modal.hide(), 2000) 300 | return 301 | } 302 | 303 | // Check how to know if user is downloading any files (check file is not from the owner) 304 | const inProgress = Object.values(this._files).some(x => x.in_progress) 305 | 306 | // Show notification modal 307 | if (inProgress) { 308 | const modal = new bootstrap.Modal(dom.notification_modal) 309 | dom.notification_modal_value.innerHTML = "Files are still downloading." 310 | modal.show() 311 | setTimeout(() => modal.hide(), 2000) 312 | return 313 | } 314 | 315 | // Show download all modal 316 | const modal = new bootstrap.Modal(dom.download_modal, { 317 | backdrop: 'static', 318 | keyboard: false 319 | }) 320 | modal.show() 321 | 322 | // Create a new zip file 323 | const zip = new JSZip(); 324 | 325 | // Start downloading all files, one by one 326 | this._downloadAll = { 327 | "active": true, 328 | "aborted": false, 329 | "file": null, 330 | "current": 0, 331 | "sizes": files.map(x => x.size), 332 | "interval": setInterval(() => this._downloadAllProgress(), 500), 333 | }; 334 | 335 | for (let file of files) { 336 | // Check if current file has been removed by the owner 337 | if (file.removed) this._downloadAll.active = false 338 | 339 | // Check if the file is still active 340 | if (!this._downloadAll.active) break 341 | 342 | // Store current file being processed 343 | this._downloadAll.file = file 344 | this._downloadAll.current += 1 345 | 346 | // Init Peering connection to receive the file 347 | await file.init() 348 | 349 | // Mark the file to be in progress 350 | file.in_progress = true 351 | 352 | // State to convert the file in zip, instead of downloading it 353 | file.zip = true 354 | 355 | // Initiate the request to the file's owner to download the file 356 | this._remotePeers[this._isHost ? file.owner_id : this._room_id].conn.send({'webrtc-file-download': {"file_id": file.id, "requester_id": this._peer.id, "requester_name": this._name, "peer_id": file.peer.id}}) 357 | 358 | // Wait until file finishes downloading 359 | await this._waitFileTranster(file) 360 | 361 | // Check if file was aborted 362 | if (file.aborted) { 363 | this._downloadAll.active = false 364 | } 365 | else { 366 | // Add files to the zip 367 | zip.file(file.name, new Blob(file.chunks)) 368 | 369 | // Clean file from memory 370 | file.chunks = [] 371 | } 372 | } 373 | 374 | // Download ZIP file 375 | if (this._downloadAll.active) { 376 | // Generate the zip file as a Blob 377 | const zipBlob = await zip.generateAsync({ type: "blob" }); 378 | 379 | // Download the zip file 380 | const downloadLink = document.createElement('a'); 381 | downloadLink.href = URL.createObjectURL(zipBlob); 382 | downloadLink.download = 'files.zip'; 383 | downloadLink.click(); 384 | } 385 | 386 | // Disable zip mode 387 | for (let file of Object.values(this._files)) file.zip = false 388 | } 389 | 390 | _waitFileTranster(file) { 391 | return new Promise((resolve) => { 392 | const checkInterval = setInterval(() => { 393 | if (!file.in_progress) { 394 | clearInterval(checkInterval); 395 | resolve(); 396 | } 397 | }, 500); 398 | }); 399 | } 400 | 401 | downloadAllCancel() { 402 | // Abort the downloadAll operation 403 | this._downloadAll.active = false 404 | this._downloadAll.aborted = true 405 | 406 | // Update UI 407 | dom.download_modal_cancel.setAttribute("disabled", "") 408 | dom.download_modal_cancel_spinner.style.display = 'inline-block' 409 | } 410 | 411 | _downloadAllProgress() { 412 | // Abort file transfer 413 | if (!this._downloadAll.active) { 414 | clearInterval(this._downloadAll.interval) 415 | } 416 | 417 | // Compute overall progress 418 | const totalSize = this._downloadAll.sizes.reduce((acc, size) => acc + size, 0); 419 | const totalTransferred = this._downloadAll.sizes.slice(0, this._downloadAll.current - 1).reduce((acc, size) => acc + size, 0) + this._downloadAll.file.transferred; 420 | const overallProgress = (totalTransferred / totalSize) * 100; 421 | 422 | // Update UI 423 | dom.download_modal_value.innerHTML = `${Math.floor(overallProgress)}%` 424 | dom.download_modal_active.querySelector('.progress-bar').style.width = `${Math.floor(overallProgress)}%` 425 | 426 | if (overallProgress == 100) { 427 | clearInterval(this._downloadAll.interval) 428 | dom.download_modal_active.style.display = 'none' 429 | dom.download_modal_success.style.display = 'flex' 430 | dom.download_modal_cancel.style.display = 'none' 431 | dom.download_modal_close.style.display = 'block' 432 | } 433 | else if (!this._downloadAll.active && !this._downloadAll.aborted) { 434 | dom.download_modal_active.style.display = 'none' 435 | dom.download_modal_error.style.display = 'block' 436 | dom.download_modal_error.querySelector('.progress-bar').style.width = `${Math.floor(overallProgress)}%` 437 | dom.download_modal_cancel.style.display = 'none' 438 | dom.download_modal_close.style.display = 'block' 439 | } 440 | else if (this._downloadAll.aborted) { 441 | const modal = bootstrap.Modal.getInstance(dom.download_modal); 442 | setTimeout(() => modal.hide(), 1000) 443 | } 444 | } 445 | 446 | // Emitted when the connection is established and ready-to-use (a peer connects to the host). 447 | _handleConnection(conn, resolve) { 448 | // console.log('Received connection from', conn.peer) 449 | 450 | // Emitted when data is received from the remote peer. 451 | conn.on('data', (data) => this._handleData(conn, data)); 452 | 453 | // Emitted when either you or the remote peer closes the data connection. 454 | conn.on('close', () => this._handleClose(conn)); 455 | 456 | // Emitted when there is an unexpected error in the data connection. 457 | conn.on('error', (err) => this._handleError(err)); 458 | 459 | if (!this._isHost) { 460 | // Store Host Peer connection 461 | this._remotePeers[conn.peer] = {"conn": conn, "interval": setInterval(() => this._isAlive(conn.peer), 1000)} 462 | 463 | // Send credentials to the host to authenticate 464 | if (!this._password) { 465 | conn.send({"webrtc-connect": {"name": this._name}}) 466 | } 467 | else { 468 | conn.send({"webrtc-connect": {"name": this._name, "password": CryptoJS.SHA3(this._password).toString(CryptoJS.enc.Hex)}}) 469 | } 470 | } 471 | 472 | // Resolve promise for .connect() method (a peer connects to the host) 473 | if (resolve !== undefined) resolve() 474 | } 475 | 476 | // Emitted when data is received from the remote peer. 477 | async _handleData(conn, data) { 478 | // console.log("Received data from", conn.peer, data) 479 | 480 | if ('webrtc-connect' in data && this._isHost) { 481 | if (this._password.length != 0 && !('password' in data['webrtc-connect'])) { 482 | conn.send({'webrtc-connect-response': {"status": "password_required"}}) 483 | } 484 | else if (this._password.length != 0 && CryptoJS.SHA3(this._password).toString(CryptoJS.enc.Hex) != data['webrtc-connect']['password']) { 485 | conn.send({'webrtc-connect-response': {"status": "password_invalid"}}) 486 | } 487 | else { 488 | // Add peer to the peers list 489 | this._remotePeers[conn.peer] = {"name": data['webrtc-connect']['name'], "conn": conn, "interval": setInterval(() => this._isAlive(conn.peer), 1000)} 490 | 491 | // Show peer connected status 492 | dom.transfer_status_wait.style.display = 'none' 493 | dom.transfer_status_success.style.display = 'inline-block' 494 | 495 | // Define peers list (including host user) 496 | const peers_list = [{"id": this._peer.id, "name": this._name }, ...Object.entries(this._remotePeers).map(([k, v]) => ({"id": k, "name": v.name}))]; 497 | 498 | // Build user's list 499 | this._addUserUI({"id": conn.peer, "name": data['webrtc-connect']['name']}) 500 | 501 | // Send confirmation 502 | conn.send({'webrtc-connect-response': {"status": "welcome", "secured": this._password.trim().length != 0}}) 503 | 504 | // Notify all peers 505 | for (let p of Object.values(this._remotePeers)) { 506 | p.conn.send({'webrtc-peers': peers_list, 'webrtc-files': Object.values(this._files).filter(x => !x.aborted && !x.removed).map(x => x.file)}) 507 | } 508 | } 509 | } 510 | else if ('webrtc-connect-response' in data && !this._isHost) { 511 | this._status = data['webrtc-connect-response'] 512 | if (data['webrtc-connect-response'].status == 'password_required') { 513 | dom.connect_div.style.display = 'none' 514 | dom.password_div.style.display = 'block' 515 | dom.password_input.focus() 516 | conn.close() 517 | } 518 | else if (data['webrtc-connect-response'].status == 'password_invalid') { 519 | dom.password_error.style.display = 'block' 520 | dom.password_input.value = '' 521 | dom.password_input.focus() 522 | dom.password_submit.removeAttribute("disabled") 523 | dom.password_loading.style.display = 'none' 524 | conn.close() 525 | } 526 | else if (data['webrtc-connect-response'].status == 'welcome') { 527 | // Update UI Components 528 | dom.connect_div.style.display = 'none' 529 | dom.password_div.style.display = 'none' 530 | dom.transfer_div.style.display = 'block'; 531 | dom.transfer_status_protected.style.display = data['webrtc-connect-response'].secured ? 'inline-block' : 'none' 532 | } 533 | } 534 | else if ('webrtc-user-name' in data && this._isHost) { 535 | // Update user 536 | this._remotePeers[data['webrtc-user-name'].id].name = data['webrtc-user-name'].name 537 | 538 | // Update files 539 | for (let f of Object.values(this._files)) { 540 | if (f.owner_id == data['webrtc-user-name'].id) { 541 | f.owner_name = data['webrtc-user-name'].name 542 | document.getElementById(`file-${f.id}-info`).innerHTML = `${this._parseBytes(f.size)} | Sent by ${f.owner_name}` 543 | } 544 | for (let p of Object.values(f.remotePeers)) { 545 | if (p.user_id == data['webrtc-user-name'].id) { 546 | p.user_name = data['webrtc-user-name'].name 547 | } 548 | } 549 | } 550 | 551 | // Update UI 552 | document.getElementById(`user-${data['webrtc-user-name'].id}-name`).innerHTML = data['webrtc-user-name'].name 553 | 554 | // Notify all peers 555 | const peers_list = [{"id": this._peer.id, "name": this._name }, ...Object.entries(this._remotePeers).map(([k, v]) => ({"id": k, "name": v.name}))]; 556 | for (let p of Object.values(this._remotePeers)) { 557 | p.conn.send({'webrtc-peers': peers_list}) 558 | } 559 | } 560 | else if ('webrtc-peers' in data && !this._isHost) { 561 | // Show transfer page 562 | dom.transfer_div.style.display = 'block' 563 | dom.transfer_status_wait.style.display = 'none' 564 | dom.transfer_status_success.style.display = 'inline-block' 565 | 566 | // Process Connected Peers 567 | for (let p of data['webrtc-peers']) { 568 | // Peer is the Host 569 | if (p.id == this._room_id) { 570 | this._remotePeers[p.id].name = p.name 571 | dom.transfer_users_list_host_name.innerHTML = p.name 572 | } 573 | // Peer is not the Host 574 | else { 575 | // If peer is new, add it to the frontend 576 | if (!(p.id in this._remotePeers)) this._addUserUI(p) 577 | // If the peer is already stored, then only update name 578 | else { 579 | this._remotePeers[p.id].name = p.name 580 | document.getElementById(`user-${p.id}-name`).innerHTML = `${p.name} ${p.id == this._peer.id ? ' (You)' : ''}` 581 | } 582 | 583 | // Store user name 584 | this._remotePeers[p.id] = {"name": p.name} 585 | } 586 | 587 | // Update files 588 | for (let f of Object.values(this._files)) { 589 | if (f.owner_id == p.id) { 590 | f.owner_name = p.name 591 | document.getElementById(`file-${f.id}-info`).innerHTML = `${this._parseBytes(f.size)} | Sent by ${f.owner_id == this._peer.id ? `${p.name} (You)` : p.name }` 592 | } 593 | } 594 | } 595 | // Check if any peer has disconnected 596 | for (let p of Object.keys(this._remotePeers)) { 597 | if (!data['webrtc-peers'].some(p2 => p2.id === p)) { 598 | clearInterval(this._remotePeers[p].interval); 599 | delete this._remotePeers[p] 600 | this._removeUserUI(p) 601 | } 602 | } 603 | 604 | // Process files 605 | if ('webrtc-files' in data) { 606 | for (let file of data['webrtc-files'].filter(x => !(x.id in this._files))) { 607 | // Create file instance 608 | const f = new File(file) 609 | 610 | // Add file to the current user 611 | this._files[f.id] = f 612 | 613 | // Add file to the list 614 | this._addFileUI(f) 615 | } 616 | } 617 | } 618 | else if ('webrtc-file-add' in data && conn.peer in this._remotePeers) { 619 | this._onFileAdd(data['webrtc-file-add']) 620 | } 621 | else if ('webrtc-file-remove' in data && conn.peer in this._remotePeers) { 622 | this._onFileRemove(data['webrtc-file-remove']) 623 | } 624 | else if ('webrtc-file-download' in data && conn.peer in this._remotePeers) { 625 | this._onFileDownload(data['webrtc-file-download']) 626 | } 627 | else conn.close() 628 | } 629 | 630 | // Emitted when either you or the remote peer closes the data connection. 631 | _handleClose(conn) { 632 | // Peer: The host has closed the connection 633 | if (conn.peer == this._room_id) { 634 | if (this._status == 'welcome') { 635 | dom.transfer_div.style.display = 'none' 636 | dom.error_div.style.display = 'block' 637 | dom.error_message.innerHTML = 'Host user has been disconnected.' 638 | } 639 | } 640 | // Host: A peer has closed the connection 641 | else if (conn.peer in this._remotePeers) { 642 | // Remove user from the list 643 | this._removeUserUI(conn.peer) 644 | 645 | // Update files 646 | for (let file of Object.values(this._files)) { 647 | if (file.owner_id == conn.peer) { 648 | document.getElementById(`file-${file.id}-abort`).style.display = 'none' 649 | document.getElementById(`file-${file.id}-download`).style.display = 'none' 650 | document.getElementById(`file-${file.id}-icon-loading`).style.display = 'none' 651 | document.getElementById(`file-${file.id}-icon-failed`).style.display = 'none' 652 | document.getElementById(`file-${file.id}-error`).style.display = 'block' 653 | document.getElementById(`file-${file.id}-error`).innerHTML = 'The user has disconnected.' 654 | file.removed = true 655 | } 656 | } 657 | 658 | // Remove peer user 659 | clearInterval(this._remotePeers[conn.peer].interval); 660 | delete this._remotePeers[conn.peer] 661 | 662 | // If no peers, disable the Send File button 663 | if (Object.keys(this._remotePeers).length == 0) { 664 | dom.transfer_status_success.style.display = 'none' 665 | dom.transfer_status_wait.style.display = 'inline-block' 666 | } 667 | 668 | // Notify all peers 669 | const peers_list = [{"id": this._peer.id, "name": this._name }, ...Object.entries(this._remotePeers).map(([k, v]) => ({"id": k, "name": v.name}))]; 670 | for (let p of Object.values(this._remotePeers)) { 671 | p.conn.send({'webrtc-peers': peers_list}) 672 | } 673 | } 674 | } 675 | 676 | // Emitted when there is an unexpected error in the data connection. 677 | _handleError(err) { 678 | console.error('Connection error: ' + err) 679 | dom.transfer_div.style.display = 'none' 680 | dom.connect_div.style.display = 'none' 681 | dom.error_div.style.display = 'block' 682 | dom.error_message.innerHTML = err.type === 'browser-incompatible' ? 'FileSync does not work with this browser.' : err.message 683 | } 684 | 685 | async _onFileAdd(files) { 686 | let data = [] 687 | for (const file of files) { 688 | // Create file instance with received data 689 | const fileData = { 690 | "id": file.id, 691 | "name": file.name, 692 | "size": file.size, 693 | "owner_id": file.owner_id, 694 | "owner_name": file.owner_name, 695 | }; 696 | 697 | // Create file instance 698 | const f = new File(fileData) 699 | 700 | // Add file to the current user 701 | this._files[f.id] = f 702 | 703 | // Add file to the list 704 | this._addFileUI(f) 705 | 706 | // Store file to send it to other peers 707 | data.push({"id": f.id, "name": f.name, "size": f.size, "owner_id": f.owner_id, "owner_name": f.owner_name}) 708 | } 709 | 710 | // Send file to all peers excluding the peer that has sent the file 711 | if (this._isHost) { 712 | const peersList = Object.entries(this._remotePeers) 713 | .filter(([peerId]) => peerId !== files[0].owner_id) 714 | .map(([, peerData]) => peerData); 715 | for (let peer of peersList) { 716 | peer.conn.send({"webrtc-file-add": data}) 717 | } 718 | } 719 | } 720 | 721 | async _onFileDownload(data) { 722 | // If the current peer is the owner of the file 723 | if (this._files[data.file_id].owner_id == this._peer.id) { 724 | this._files[data.file_id].transfer(data) 725 | } 726 | // Redirect 727 | else { 728 | const owner_id = this._files[data.file_id].owner_id 729 | this._remotePeers[owner_id].conn.send({"webrtc-file-download": data}) 730 | } 731 | } 732 | 733 | _onFileRemove(data) { 734 | // Abort the file transfer 735 | this._files[data.file_id].remove() 736 | 737 | document.getElementById(`file-${data.file_id}-abort`).style.display = 'none' 738 | document.getElementById(`file-${data.file_id}-download`).style.display = 'none' 739 | document.getElementById(`file-${data.file_id}-icon-loading`).style.display = 'none' 740 | document.getElementById(`file-${data.file_id}-icon-failed`).style.display = 'none' 741 | document.getElementById(`file-${data.file_id}-error`).style.display = 'block' 742 | document.getElementById(`file-${data.file_id}-error`).innerHTML = 'This file has been remove it.' 743 | 744 | // Send file to all peers excluding the peer that has sent the file 745 | if (this._isHost) { 746 | const peersList = Object.entries(this._remotePeers) 747 | .filter(([peerId]) => peerId !== data.peer_id) 748 | .map(([, peerData]) => peerData); 749 | for (let peer of peersList) { 750 | peer.conn.send({"webrtc-file-remove": data}) 751 | } 752 | } 753 | } 754 | 755 | _addUserUI(user) { 756 | // Add new user 757 | let li = document.createElement('li') 758 | li.setAttribute('id', `user-${user.id}`) 759 | li.setAttribute('class', 'list-group-item') 760 | li.innerHTML = ` 761 | 762 | 763 | 764 | 765 | 766 | ${user.id == this._peer.id ? `${user.name} (You)` : user.name} 767 | ` 768 | dom.transfer_users_list.appendChild(li) 769 | 770 | // Update the number of users in the list 771 | dom.transfer_users_count.innerHTML = ` (${dom.transfer_users_list.querySelectorAll('li').length})` 772 | } 773 | 774 | _removeUserUI(user_id) { 775 | // Remove user from the list 776 | document.getElementById(`user-${user_id}`).remove() 777 | 778 | // Update the number of users in the list 779 | dom.transfer_users_count.innerHTML = ` (${dom.transfer_users_list.querySelectorAll('li').length})` 780 | } 781 | 782 | _addFileUI(file) { 783 | // Add file to the list 784 | dom.transfer_files_list_empty.remove() 785 | let li = document.createElement('li') 786 | li.setAttribute('id', `file-${this._peer.id}`) 787 | li.setAttribute('class', 'list-group-item') 788 | 789 | li.innerHTML = ` 790 |
791 |
792 | 793 | 798 | 803 |
804 | 805 |
806 |
807 | 808 | 809 | 810 | 811 | 812 | 813 | ${file.name}
814 |
${this._parseBytes(file.size)} | Sent by ${file.owner_id == this._peer.id ? `${file.owner_name} (You)` : file.owner_name }
815 | 816 |
See details
817 |
818 | 819 |
820 | 821 | 822 | 823 | 824 |
825 | 831 |
832 | 833 | 834 | 835 |
836 |
837 | ` 838 | dom.transfer_files_list.appendChild(li) 839 | 840 | // Update the number of files in the list 841 | dom.transfer_files_count.innerHTML = ` (${dom.transfer_files_list.querySelectorAll('li').length})` 842 | } 843 | 844 | _generate_name() { 845 | const colors = ["Aqua","Aquamarine","Azure","Beige","Bisque","Black","Blue","Brown","Chartreuse","Chocolate","Coral","Cornsilk","Crimson","Cyan","Fuchsia","Gold","Gray","Grey","Green","Indigo","Ivory","Khaki","Lavender","Lime","Linen","Magenta","Maroon","Navy","Olive","Orange","Orchid","Peru","Pink","Plum","Purple","Red","Salmon","Sienna","Silver","Snow","Tan","Teal","Thistle","Tomato","Turquoise","Violet","Wheat","White","Yellow"] 846 | const animals = ["Aardvark","Albatross","Alligator","Alpaca","Ant","Anteater","Antelope","Ape","Armadillo","Donkey","Baboon","Badger","Barracuda","Bat","Bear","Beaver","Bee","Bison","Boar","Buffalo","Butterfly","Camel","Capybara","Caribou","Cassowary","Cat","Caterpillar","Cattle","Chamois","Cheetah","Chicken","Chimpanzee","Chinchilla","Chough","Clam","Cobra","Cockroach","Cod","Cormorant","Coyote","Crab","Crane","Crocodile","Crow","Curlew","Deer","Dinosaur","Dog","Dogfish","Dolphin","Dotterel","Dove","Dragonfly","Duck","Dugong","Dunlin","Eagle","Echidna","Eel","Eland","Elephant","Elk","Emu","Falcon","Ferret","Finch","Fish","Flamingo","Fly","Fox","Frog","Gaur","Gazelle","Gerbil","Giraffe","Gnat","Gnu","Goat","Goldfinch","Goldfish","Goose","Gorilla","Goshawk","Grasshopper","Grouse","Guanaco","Gull","Hamster","Hare","Hawk","Hedgehog","Heron","Herring","Hippopotamus","Hornet","Horse","Human","Hummingbird","Hyena","Ibex","Ibis","Jackal","Jaguar","Jay","Jellyfish","Kangaroo","Kingfisher","Koala","Kookabura","Kouprey","Kudu","Lapwing","Lark","Lemur","Leopard","Lion","Llama","Lobster","Locust","Loris","Louse","Lyrebird","Magpie","Mallard","Manatee","Mandrill","Mantis","Marten","Meerkat","Mink","Mole","Mongoose","Monkey","Moose","Mosquito","Mouse","Mule","Narwhal","Newt","Nightingale","Octopus","Okapi","Opossum","Oryx","Ostrich","Otter","Owl","Oyster","Panther","Parrot","Partridge","Peafowl","Pelican","Penguin","Pheasant","Pig","Pigeon","Pony","Porcupine","Porpoise","Quail","Quelea","Quetzal","Rabbit","Raccoon","Rail","Ram","Rat","Raven","Red deer","Red panda","Reindeer","Rhinoceros","Rook","Salamander","Salmon","Sand Dollar","Sandpiper","Sardine","Scorpion","Seahorse","Seal","Shark","Sheep","Shrew","Skunk","Snail","Snake","Sparrow","Spider","Spoonbill","Squid","Squirrel","Starling","Stingray","Stinkbug","Stork","Swallow","Swan","Tapir","Tarsier","Termite","Tiger","Toad","Trout","Turkey","Turtle","Viper","Vulture","Wallaby","Walrus","Wasp","Weasel","Whale","Wildcat","Wolf","Wolverine","Wombat","Woodcock","Woodpecker","Worm","Wren","Yak","Zebra"] 847 | return colors[Math.round(Math.random() * (colors.length - 1))] + ' ' + animals[Math.round(Math.random() * (animals.length - 1))] 848 | } 849 | 850 | // Function to parse bytes 851 | _parseBytes(bytes) { 852 | const units = ['bytes', 'KB', 'MB', 'GB', 'TB'] 853 | const base = 1024 854 | if (bytes === 0) { 855 | return '0 bytes' 856 | } 857 | const exponent = Math.floor(Math.log(bytes) / Math.log(base)) 858 | const value = (bytes / Math.pow(base, exponent)).toFixed(2) 859 | return `${value} ${units[exponent]}` 860 | } 861 | 862 | async _getUUID() { 863 | const response = await fetch(`/api/uuid`); 864 | const data = await response.json(); 865 | return data['uuid']; 866 | } 867 | } -------------------------------------------------------------------------------- /web/js/vendors/crypto-js.min.js: -------------------------------------------------------------------------------- 1 | // v.4.2.0 2 | !function(t,e){"object"==typeof exports?module.exports=exports=e():"function"==typeof define&&define.amd?define([],e):t.CryptoJS=e()}(this,function(){var W,O,I,U,K,X,L,l,j,T,t,N,q,e,Z,V,G,J,Q,Y,$,t1,e1,r1,i1,o1,n1,s,s1,c1,a1,h1,l1,o,f1,r,d1,u1,n,c,a,h,f,d,i=function(h){var i;if("undefined"!=typeof window&&window.crypto&&(i=window.crypto),"undefined"!=typeof self&&self.crypto&&(i=self.crypto),!(i=!(i=!(i="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:i)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:i)&&"undefined"!=typeof global&&global.crypto?global.crypto:i)&&"function"==typeof require)try{i=require("crypto")}catch(t){}var r=Object.create||function(t){return e.prototype=t,t=new e,e.prototype=null,t};function e(){}var t={},o=t.lib={},n=o.Base={extend:function(t){var e=r(this);return t&&e.mixIn(t),e.hasOwnProperty("init")&&this.init!==e.init||(e.init=function(){e.$super.init.apply(this,arguments)}),(e.init.prototype=e).$super=this,e},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},l=o.WordArray=n.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var e=this.words,r=t.words,i=this.sigBytes,o=t.sigBytes;if(this.clamp(),i%4)for(var n=0;n>>2]>>>24-n%4*8&255;e[i+n>>>2]|=s<<24-(i+n)%4*8}else for(var c=0;c>>2]=r[c>>>2];return this.sigBytes+=o,this},clamp:function(){var t=this.words,e=this.sigBytes;t[e>>>2]&=4294967295<<32-e%4*8,t.length=h.ceil(e/4)},clone:function(){var t=n.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var e=[],r=0;r>>2]>>>24-o%4*8&255;i.push((n>>>4).toString(16)),i.push((15&n).toString(16))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new l.init(r,e/2)}},a=s.Latin1={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>24-o%4*8&255;i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new l.init(r,e)}},f=s.Utf8={stringify:function(t){try{return decodeURIComponent(escape(a.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return a.parse(unescape(encodeURIComponent(t)))}},d=o.BufferedBlockAlgorithm=n.extend({reset:function(){this._data=new l.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var e,r=this._data,i=r.words,o=r.sigBytes,n=this.blockSize,s=o/(4*n),c=(s=t?h.ceil(s):h.max((0|s)-this._minBufferSize,0))*n,t=h.min(4*c,o);if(c){for(var a=0;a>>2]|=t[i]<<24-i%4*8;I.call(this,r,e)}else I.apply(this,arguments)}).prototype=p),i),p1=u.lib.WordArray;function _1(t){return t<<8&4278255360|t>>>8&16711935}(u=u.enc).Utf16=u.Utf16BE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>16-o%4*8&65535;i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=t.charCodeAt(i)<<16-i%2*16;return p1.create(r,2*e)}},u.Utf16LE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>16-o%4*8&65535);i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=_1(t.charCodeAt(i)<<16-i%2*16);return p1.create(r,2*e)}},U=(p=i).lib.WordArray,p.enc.Base64={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=this._map,o=(t.clamp(),[]),n=0;n>>2]>>>24-n%4*8&255)<<16|(e[n+1>>>2]>>>24-(n+1)%4*8&255)<<8|e[n+2>>>2]>>>24-(n+2)%4*8&255,c=0;c<4&&n+.75*c>>6*(3-c)&63));var a=i.charAt(64);if(a)for(;o.length%4;)o.push(a);return o.join("")},parse:function(t){var e=t.length,r=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],o=0;o>>6-u%4*2,s=s|n,f[d>>>2]|=s<<24-d%4*8,d++);return U.create(f,d)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},K=(u=i).lib.WordArray,u.enc.Base64url={stringify:function(t,e){for(var r=t.words,i=t.sigBytes,o=(e=void 0===e?!0:e)?this._safe_map:this._map,n=(t.clamp(),[]),s=0;s>>2]>>>24-s%4*8&255)<<16|(r[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|r[s+2>>>2]>>>24-(s+2)%4*8&255,a=0;a<4&&s+.75*a>>6*(3-a)&63));var h=o.charAt(64);if(h)for(;n.length%4;)n.push(h);return n.join("")},parse:function(t,e){var r=t.length,i=(e=void 0===e?!0:e)?this._safe_map:this._map;if(!(o=this._reverseMap))for(var o=this._reverseMap=[],n=0;n>>6-u%4*2,c=c|s,f[d>>>2]|=c<<24-d%4*8,d++);return K.create(f,d)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_safe_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"};for(var y1=Math,p=i,g1=(u=p.lib).WordArray,v1=u.Hasher,u=p.algo,A=[],B1=0;B1<64;B1++)A[B1]=4294967296*y1.abs(y1.sin(B1+1))|0;function z(t,e,r,i,o,n,s){t=t+(e&r|~e&i)+o+s;return(t<>>32-n)+e}function H(t,e,r,i,o,n,s){t=t+(e&i|r&~i)+o+s;return(t<>>32-n)+e}function C(t,e,r,i,o,n,s){t=t+(e^r^i)+o+s;return(t<>>32-n)+e}function R(t,e,r,i,o,n,s){t=t+(r^(e|~i))+o+s;return(t<>>32-n)+e}u=u.MD5=v1.extend({_doReset:function(){this._hash=new g1.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,o=t[i];t[i]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}var n=this._hash.words,s=t[e+0],c=t[e+1],a=t[e+2],h=t[e+3],l=t[e+4],f=t[e+5],d=t[e+6],u=t[e+7],p=t[e+8],_=t[e+9],y=t[e+10],g=t[e+11],v=t[e+12],B=t[e+13],w=t[e+14],k=t[e+15],x=z(n[0],S=n[1],m=n[2],b=n[3],s,7,A[0]),b=z(b,x,S,m,c,12,A[1]),m=z(m,b,x,S,a,17,A[2]),S=z(S,m,b,x,h,22,A[3]);x=z(x,S,m,b,l,7,A[4]),b=z(b,x,S,m,f,12,A[5]),m=z(m,b,x,S,d,17,A[6]),S=z(S,m,b,x,u,22,A[7]),x=z(x,S,m,b,p,7,A[8]),b=z(b,x,S,m,_,12,A[9]),m=z(m,b,x,S,y,17,A[10]),S=z(S,m,b,x,g,22,A[11]),x=z(x,S,m,b,v,7,A[12]),b=z(b,x,S,m,B,12,A[13]),m=z(m,b,x,S,w,17,A[14]),x=H(x,S=z(S,m,b,x,k,22,A[15]),m,b,c,5,A[16]),b=H(b,x,S,m,d,9,A[17]),m=H(m,b,x,S,g,14,A[18]),S=H(S,m,b,x,s,20,A[19]),x=H(x,S,m,b,f,5,A[20]),b=H(b,x,S,m,y,9,A[21]),m=H(m,b,x,S,k,14,A[22]),S=H(S,m,b,x,l,20,A[23]),x=H(x,S,m,b,_,5,A[24]),b=H(b,x,S,m,w,9,A[25]),m=H(m,b,x,S,h,14,A[26]),S=H(S,m,b,x,p,20,A[27]),x=H(x,S,m,b,B,5,A[28]),b=H(b,x,S,m,a,9,A[29]),m=H(m,b,x,S,u,14,A[30]),x=C(x,S=H(S,m,b,x,v,20,A[31]),m,b,f,4,A[32]),b=C(b,x,S,m,p,11,A[33]),m=C(m,b,x,S,g,16,A[34]),S=C(S,m,b,x,w,23,A[35]),x=C(x,S,m,b,c,4,A[36]),b=C(b,x,S,m,l,11,A[37]),m=C(m,b,x,S,u,16,A[38]),S=C(S,m,b,x,y,23,A[39]),x=C(x,S,m,b,B,4,A[40]),b=C(b,x,S,m,s,11,A[41]),m=C(m,b,x,S,h,16,A[42]),S=C(S,m,b,x,d,23,A[43]),x=C(x,S,m,b,_,4,A[44]),b=C(b,x,S,m,v,11,A[45]),m=C(m,b,x,S,k,16,A[46]),x=R(x,S=C(S,m,b,x,a,23,A[47]),m,b,s,6,A[48]),b=R(b,x,S,m,u,10,A[49]),m=R(m,b,x,S,w,15,A[50]),S=R(S,m,b,x,f,21,A[51]),x=R(x,S,m,b,v,6,A[52]),b=R(b,x,S,m,h,10,A[53]),m=R(m,b,x,S,y,15,A[54]),S=R(S,m,b,x,c,21,A[55]),x=R(x,S,m,b,p,6,A[56]),b=R(b,x,S,m,k,10,A[57]),m=R(m,b,x,S,d,15,A[58]),S=R(S,m,b,x,B,21,A[59]),x=R(x,S,m,b,l,6,A[60]),b=R(b,x,S,m,g,10,A[61]),m=R(m,b,x,S,a,15,A[62]),S=R(S,m,b,x,_,21,A[63]),n[0]=n[0]+x|0,n[1]=n[1]+S|0,n[2]=n[2]+m|0,n[3]=n[3]+b|0},_doFinalize:function(){for(var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,o=(e[i>>>5]|=128<<24-i%32,y1.floor(r/4294967296)),o=(e[15+(64+i>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process(),this._hash),n=o.words,s=0;s<4;s++){var c=n[s];n[s]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}return o},clone:function(){var t=v1.clone.call(this);return t._hash=this._hash.clone(),t}}),p.MD5=v1._createHelper(u),p.HmacMD5=v1._createHmacHelper(u),u=(p=i).lib,X=u.WordArray,L=u.Hasher,u=p.algo,l=[],u=u.SHA1=L.extend({_doReset:function(){this._hash=new X.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],o=r[1],n=r[2],s=r[3],c=r[4],a=0;a<80;a++){a<16?l[a]=0|t[e+a]:(h=l[a-3]^l[a-8]^l[a-14]^l[a-16],l[a]=h<<1|h>>>31);var h=(i<<5|i>>>27)+c+l[a];h+=a<20?1518500249+(o&n|~o&s):a<40?1859775393+(o^n^s):a<60?(o&n|o&s|n&s)-1894007588:(o^n^s)-899497514,c=s,s=n,n=o<<30|o>>>2,o=i,i=h}r[0]=r[0]+i|0,r[1]=r[1]+o|0,r[2]=r[2]+n|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=Math.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=L.clone.call(this);return t._hash=this._hash.clone(),t}}),p.SHA1=L._createHelper(u),p.HmacSHA1=L._createHmacHelper(u);var w1=Math,p=i,k1=(u=p.lib).WordArray,x1=u.Hasher,u=p.algo,b1=[],m1=[];function S1(t){return 4294967296*(t-(0|t))|0}for(var A1=2,z1=0;z1<64;)!function(t){for(var e=w1.sqrt(t),r=2;r<=e;r++)if(!(t%r))return;return 1}(A1)||(z1<8&&(b1[z1]=S1(w1.pow(A1,.5))),m1[z1]=S1(w1.pow(A1,1/3)),z1++),A1++;var _=[],u=u.SHA256=x1.extend({_doReset:function(){this._hash=new k1.init(b1.slice(0))},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],o=r[1],n=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=0;f<64;f++){f<16?_[f]=0|t[e+f]:(d=_[f-15],u=_[f-2],_[f]=((d<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+_[f-7]+((u<<15|u>>>17)^(u<<13|u>>>19)^u>>>10)+_[f-16]);var d=i&o^i&n^o&n,u=l+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&a^~c&h)+m1[f]+_[f],l=h,h=a,a=c,c=s+u|0,s=n,n=o,o=i,i=u+(((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+d)|0}r[0]=r[0]+i|0,r[1]=r[1]+o|0,r[2]=r[2]+n|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0,r[5]=r[5]+a|0,r[6]=r[6]+h|0,r[7]=r[7]+l|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=w1.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=x1.clone.call(this);return t._hash=this._hash.clone(),t}}),p=(p.SHA256=x1._createHelper(u),p.HmacSHA256=x1._createHmacHelper(u),j=(p=i).lib.WordArray,u=p.algo,T=u.SHA256,u=u.SHA224=T.extend({_doReset:function(){this._hash=new j.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var t=T._doFinalize.call(this);return t.sigBytes-=4,t}}),p.SHA224=T._createHelper(u),p.HmacSHA224=T._createHmacHelper(u),i),H1=p.lib.Hasher,y=(u=p.x64).Word,C1=u.WordArray,u=p.algo;function g(){return y.create.apply(y,arguments)}for(var R1=[g(1116352408,3609767458),g(1899447441,602891725),g(3049323471,3964484399),g(3921009573,2173295548),g(961987163,4081628472),g(1508970993,3053834265),g(2453635748,2937671579),g(2870763221,3664609560),g(3624381080,2734883394),g(310598401,1164996542),g(607225278,1323610764),g(1426881987,3590304994),g(1925078388,4068182383),g(2162078206,991336113),g(2614888103,633803317),g(3248222580,3479774868),g(3835390401,2666613458),g(4022224774,944711139),g(264347078,2341262773),g(604807628,2007800933),g(770255983,1495990901),g(1249150122,1856431235),g(1555081692,3175218132),g(1996064986,2198950837),g(2554220882,3999719339),g(2821834349,766784016),g(2952996808,2566594879),g(3210313671,3203337956),g(3336571891,1034457026),g(3584528711,2466948901),g(113926993,3758326383),g(338241895,168717936),g(666307205,1188179964),g(773529912,1546045734),g(1294757372,1522805485),g(1396182291,2643833823),g(1695183700,2343527390),g(1986661051,1014477480),g(2177026350,1206759142),g(2456956037,344077627),g(2730485921,1290863460),g(2820302411,3158454273),g(3259730800,3505952657),g(3345764771,106217008),g(3516065817,3606008344),g(3600352804,1432725776),g(4094571909,1467031594),g(275423344,851169720),g(430227734,3100823752),g(506948616,1363258195),g(659060556,3750685593),g(883997877,3785050280),g(958139571,3318307427),g(1322822218,3812723403),g(1537002063,2003034995),g(1747873779,3602036899),g(1955562222,1575990012),g(2024104815,1125592928),g(2227730452,2716904306),g(2361852424,442776044),g(2428436474,593698344),g(2756734187,3733110249),g(3204031479,2999351573),g(3329325298,3815920427),g(3391569614,3928383900),g(3515267271,566280711),g(3940187606,3454069534),g(4118630271,4000239992),g(116418474,1914138554),g(174292421,2731055270),g(289380356,3203993006),g(460393269,320620315),g(685471733,587496836),g(852142971,1086792851),g(1017036298,365543100),g(1126000580,2618297676),g(1288033470,3409855158),g(1501505948,4234509866),g(1607167915,987167468),g(1816402316,1246189591)],D1=[],E1=0;E1<80;E1++)D1[E1]=g();u=u.SHA512=H1.extend({_doReset:function(){this._hash=new C1.init([new y.init(1779033703,4089235720),new y.init(3144134277,2227873595),new y.init(1013904242,4271175723),new y.init(2773480762,1595750129),new y.init(1359893119,2917565137),new y.init(2600822924,725511199),new y.init(528734635,4215389547),new y.init(1541459225,327033209)])},_doProcessBlock:function(W,O){for(var t=this._hash.words,e=t[0],r=t[1],i=t[2],o=t[3],n=t[4],s=t[5],c=t[6],t=t[7],I=e.high,a=e.low,U=r.high,h=r.low,K=i.high,l=i.low,X=o.high,f=o.low,L=n.high,d=n.low,j=s.high,u=s.low,T=c.high,p=c.low,N=t.high,_=t.low,y=I,g=a,v=U,B=h,w=K,k=l,q=X,x=f,b=L,m=d,Z=j,S=u,V=T,G=p,J=N,Q=_,A=0;A<80;A++)var z,H,C=D1[A],R=(A<16?(H=C.high=0|W[O+2*A],z=C.low=0|W[O+2*A+1]):(F=(P=D1[A-15]).high,P=P.low,M=(E=D1[A-2]).high,E=E.low,D=(R=D1[A-7]).high,R=R.low,$=(Y=D1[A-16]).high,H=(H=((F>>>1|P<<31)^(F>>>8|P<<24)^F>>>7)+D+((z=(D=(P>>>1|F<<31)^(P>>>8|F<<24)^(P>>>7|F<<25))+R)>>>0>>0?1:0))+((M>>>19|E<<13)^(M<<3|E>>>29)^M>>>6)+((z+=P=(E>>>19|M<<13)^(E<<3|M>>>29)^(E>>>6|M<<26))>>>0

>>0?1:0),z+=F=Y.low,C.high=H=H+$+(z>>>0>>0?1:0),C.low=z),b&Z^~b&V),D=m&S^~m&G,E=y&v^y&w^v&w,M=(g>>>28|y<<4)^(g<<30|y>>>2)^(g<<25|y>>>7),P=R1[A],Y=P.high,$=P.low,F=Q+((m>>>14|b<<18)^(m>>>18|b<<14)^(m<<23|b>>>9)),C=J+((b>>>14|m<<18)^(b>>>18|m<<14)^(b<<23|m>>>9))+(F>>>0>>0?1:0),t1=M+(g&B^g&k^B&k),J=V,Q=G,V=Z,G=S,Z=b,S=m,b=q+(C=C+R+((F=F+D)>>>0>>0?1:0)+Y+((F=F+$)>>>0<$>>>0?1:0)+H+((F=F+z)>>>0>>0?1:0))+((m=x+F|0)>>>0>>0?1:0)|0,q=w,x=k,w=v,k=B,v=y,B=g,y=C+(((y>>>28|g<<4)^(y<<30|g>>>2)^(y<<25|g>>>7))+E+(t1>>>0>>0?1:0))+((g=F+t1|0)>>>0>>0?1:0)|0;a=e.low=a+g,e.high=I+y+(a>>>0>>0?1:0),h=r.low=h+B,r.high=U+v+(h>>>0>>0?1:0),l=i.low=l+k,i.high=K+w+(l>>>0>>0?1:0),f=o.low=f+x,o.high=X+q+(f>>>0>>0?1:0),d=n.low=d+m,n.high=L+b+(d>>>0>>0?1:0),u=s.low=u+S,s.high=j+Z+(u>>>0>>0?1:0),p=c.low=p+G,c.high=T+V+(p>>>0>>0?1:0),_=t.low=_+Q,t.high=N+J+(_>>>0>>0?1:0)},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[30+(128+i>>>10<<5)]=Math.floor(r/4294967296),e[31+(128+i>>>10<<5)]=r,t.sigBytes=4*e.length,this._process(),this._hash.toX32()},clone:function(){var t=H1.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32}),p.SHA512=H1._createHelper(u),p.HmacSHA512=H1._createHmacHelper(u),u=(p=i).x64,t=u.Word,N=u.WordArray,u=p.algo,q=u.SHA512,u=u.SHA384=q.extend({_doReset:function(){this._hash=new N.init([new t.init(3418070365,3238371032),new t.init(1654270250,914150663),new t.init(2438529370,812702999),new t.init(355462360,4144912697),new t.init(1731405415,4290775857),new t.init(2394180231,1750603025),new t.init(3675008525,1694076839),new t.init(1203062813,3204075428)])},_doFinalize:function(){var t=q._doFinalize.call(this);return t.sigBytes-=16,t}}),p.SHA384=q._createHelper(u),p.HmacSHA384=q._createHmacHelper(u);for(var M1=Math,p=i,P1=(u=p.lib).WordArray,F1=u.Hasher,W1=p.x64.Word,u=p.algo,O1=[],I1=[],U1=[],v=1,B=0,K1=0;K1<24;K1++){O1[v+5*B]=(K1+1)*(K1+2)/2%64;var X1=(2*v+3*B)%5;v=B%5,B=X1}for(v=0;v<5;v++)for(B=0;B<5;B++)I1[v+5*B]=B+(2*v+3*B)%5*5;for(var L1=1,j1=0;j1<24;j1++){for(var T1,N1=0,q1=0,Z1=0;Z1<7;Z1++)1&L1&&((T1=(1<>>32-e}function Y1(t){return"string"==typeof t?f1:o}function $1(t,e,r){var i,o=this._iv;o?(i=o,this._iv=void 0):i=this._prevBlock;for(var n=0;n>24&255)?(r=t>>8&255,i=255&t,255===(e=t>>16&255)?(e=0,255===r?(r=0,255===i?i=0:++i):++r):++e,t=0,t=(t+=e<<16)+(r<<8)+i):t+=1<<24,t}u=u.SHA3=F1.extend({cfg:F1.cfg.extend({outputLength:512}),_doReset:function(){for(var t=this._state=[],e=0;e<25;e++)t[e]=new W1.init;this.blockSize=(1600-2*this.cfg.outputLength)/32},_doProcessBlock:function(t,e){for(var r=this._state,i=this.blockSize/2,o=0;o>>24)|4278255360&(n<<24|n>>>8);(x=r[o]).high^=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),x.low^=n}for(var c=0;c<24;c++){for(var a=0;a<5;a++){for(var h=0,l=0,f=0;f<5;f++)h^=(x=r[a+5*f]).high,l^=x.low;var d=D[a];d.high=h,d.low=l}for(a=0;a<5;a++)for(var u=D[(a+4)%5],p=D[(a+1)%5],_=p.high,p=p.low,h=u.high^(_<<1|p>>>31),l=u.low^(p<<1|_>>>31),f=0;f<5;f++)(x=r[a+5*f]).high^=h,x.low^=l;for(var y=1;y<25;y++){var g=(x=r[y]).high,v=x.low,B=O1[y],g=(l=B<32?(h=g<>>32-B,v<>>32-B):(h=v<>>64-B,g<>>64-B),D[I1[y]]);g.high=h,g.low=l}var w=D[0],k=r[0];w.high=k.high,w.low=k.low;for(a=0;a<5;a++)for(f=0;f<5;f++){var x=r[y=a+5*f],b=D[y],m=D[(a+1)%5+5*f],S=D[(a+2)%5+5*f];x.high=b.high^~m.high&S.high,x.low=b.low^~m.low&S.low}x=r[0],w=U1[c];x.high^=w.high,x.low^=w.low}},_doFinalize:function(){for(var t=this._data,e=t.words,r=(this._nDataBytes,8*t.sigBytes),i=32*this.blockSize,o=(e[r>>>5]|=1<<24-r%32,e[(M1.ceil((1+r)/i)*i>>>5)-1]|=128,t.sigBytes=4*e.length,this._process(),this._state),r=this.cfg.outputLength/8,n=r/8,s=[],c=0;c>>24)|4278255360&(h<<24|h>>>8);s.push(16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)),s.push(h)}return new P1.init(s,r)},clone:function(){for(var t=F1.clone.call(this),e=t._state=this._state.slice(0),r=0;r<25;r++)e[r]=e[r].clone();return t}}),p.SHA3=F1._createHelper(u),p.HmacSHA3=F1._createHmacHelper(u),Math,u=(p=i).lib,e=u.WordArray,Z=u.Hasher,u=p.algo,V=e.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),G=e.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),J=e.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),Q=e.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),Y=e.create([0,1518500249,1859775393,2400959708,2840853838]),$=e.create([1352829926,1548603684,1836072691,2053994217,0]),u=u.RIPEMD160=Z.extend({_doReset:function(){this._hash=e.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,o=t[i];t[i]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}for(var n,s,c,a,h,l,f=this._hash.words,d=Y.words,u=$.words,p=V.words,_=G.words,y=J.words,g=Q.words,v=n=f[0],B=s=f[1],w=c=f[2],k=a=f[3],x=h=f[4],r=0;r<80;r+=1)l=(l=Q1(l=(l=n+t[e+p[r]]|0)+(r<16?(s^c^a)+d[0]:r<32?G1(s,c,a)+d[1]:r<48?((s|~c)^a)+d[2]:r<64?J1(s,c,a)+d[3]:(s^(c|~a))+d[4])|0,y[r]))+h|0,n=h,h=a,a=Q1(c,10),c=s,s=l,l=(l=Q1(l=(l=v+t[e+_[r]]|0)+(r<16?(B^(w|~k))+u[0]:r<32?J1(B,w,k)+u[1]:r<48?((B|~w)^k)+u[2]:r<64?G1(B,w,k)+u[3]:(B^w^k)+u[4])|0,g[r]))+x|0,v=x,x=k,k=Q1(w,10),w=B,B=l;l=f[1]+c+k|0,f[1]=f[2]+a+x|0,f[2]=f[3]+h+v|0,f[3]=f[4]+n+B|0,f[4]=f[0]+s+w|0,f[0]=l},_doFinalize:function(){for(var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,i=(e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process(),this._hash),o=i.words,n=0;n<5;n++){var s=o[n];o[n]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8)}return i},clone:function(){var t=Z.clone.call(this);return t._hash=this._hash.clone(),t}}),p.RIPEMD160=Z._createHelper(u),p.HmacRIPEMD160=Z._createHmacHelper(u),u=(p=i).lib.Base,t1=p.enc.Utf8,p.algo.HMAC=u.extend({init:function(t,e){t=this._hasher=new t.init,"string"==typeof e&&(e=t1.parse(e));for(var r=t.blockSize,i=4*r,t=((e=e.sigBytes>i?t.finalize(e):e).clamp(),this._oKey=e.clone()),e=this._iKey=e.clone(),o=t.words,n=e.words,s=0;s>>2];t.sigBytes-=e}},P.BlockCipher=h1.extend({cfg:h1.cfg.extend({mode:r,padding:u}),reset:function(){h1.reset.call(this);var t,e=this.cfg,r=e.iv,e=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=e.createEncryptor:(t=e.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,r&&r.words):(this._mode=t.call(e,this,r&&r.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),l1=P.CipherParams=p.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}}),r=(w.format={}).OpenSSL={stringify:function(t){var e=t.ciphertext,t=t.salt,t=t?s.create([1398893684,1701076831]).concat(t).concat(e):e;return t.toString(c1)},parse:function(t){var e,t=c1.parse(t),r=t.words;return 1398893684==r[0]&&1701076831==r[1]&&(e=s.create(r.slice(2,4)),r.splice(0,4),t.sigBytes-=16),l1.create({ciphertext:t,salt:e})}},o=P.SerializableCipher=p.extend({cfg:p.extend({format:r}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var o=t.createEncryptor(r,i),e=o.finalize(e),o=o.cfg;return l1.create({ciphertext:e,key:r,iv:o.iv,algorithm:t,mode:o.mode,padding:o.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){return i=this.cfg.extend(i),e=this._parse(e,i.format),t.createDecryptor(r,i).finalize(e.ciphertext)},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),u=(w.kdf={}).OpenSSL={execute:function(t,e,r,i,o){i=i||s.random(8),o=(o?a1.create({keySize:e+r,hasher:o}):a1.create({keySize:e+r})).compute(t,i);t=s.create(o.words.slice(e),4*r);return o.sigBytes=4*e,l1.create({key:o,iv:t,salt:i})}},f1=P.PasswordBasedCipher=o.extend({cfg:o.cfg.extend({kdf:u}),encrypt:function(t,e,r,i){r=(i=this.cfg.extend(i)).kdf.execute(r,t.keySize,t.ivSize,i.salt,i.hasher),i.iv=r.iv,t=o.encrypt.call(this,t,e,r.key,i);return t.mixIn(r),t},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);r=i.kdf.execute(r,t.keySize,t.ivSize,e.salt,i.hasher);return i.iv=r.iv,o.decrypt.call(this,t,e,r.key,i)}})),i.mode.CFB=((p=i.lib.BlockCipherMode.extend()).Encryptor=p.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize;t2.call(this,t,e,i,r),this._prevBlock=t.slice(e,e+i)}}),p.Decryptor=p.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,o=t.slice(e,e+i);t2.call(this,t,e,i,r),this._prevBlock=o}}),p),i.mode.CTR=(r=i.lib.BlockCipherMode.extend(),w=r.Encryptor=r.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,o=this._iv,n=this._counter,s=(o&&(n=this._counter=o.slice(0),this._iv=void 0),n.slice(0));r.encryptBlock(s,0),n[i-1]=n[i-1]+1|0;for(var c=0;c>>2]|=e<<24-r%4*8,t.sigBytes+=e},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},i.pad.Iso10126={pad:function(t,e){e*=4,e-=t.sigBytes%e;t.concat(i.lib.WordArray.random(e-1)).concat(i.lib.WordArray.create([e<<24],1))},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},i.pad.Iso97971={pad:function(t,e){t.concat(i.lib.WordArray.create([2147483648],1)),i.pad.ZeroPadding.pad(t,e)},unpad:function(t){i.pad.ZeroPadding.unpad(t),t.sigBytes--}},i.pad.ZeroPadding={pad:function(t,e){e*=4;t.clamp(),t.sigBytes+=e-(t.sigBytes%e||e)},unpad:function(t){for(var e=t.words,r=t.sigBytes-1,r=t.sigBytes-1;0<=r;r--)if(e[r>>>2]>>>24-r%4*8&255){t.sigBytes=r+1;break}}},i.pad.NoPadding={pad:function(){},unpad:function(){}},d1=(P=i).lib.CipherParams,u1=P.enc.Hex,P.format.Hex={stringify:function(t){return t.ciphertext.toString(u1)},parse:function(t){t=u1.parse(t);return d1.create({ciphertext:t})}};for(var w=i,p=w.lib.BlockCipher,u=w.algo,k=[],r2=[],i2=[],o2=[],n2=[],s2=[],c2=[],a2=[],h2=[],l2=[],x=[],b=0;b<256;b++)x[b]=b<128?b<<1:b<<1^283;for(var m=0,S=0,b=0;b<256;b++){var E=S^S<<1^S<<2^S<<3^S<<4,f2=(k[m]=E=E>>>8^255&E^99,x[r2[E]=m]),d2=x[f2],u2=x[d2],M=257*x[E]^16843008*E;i2[m]=M<<24|M>>>8,o2[m]=M<<16|M>>>16,n2[m]=M<<8|M>>>24,s2[m]=M,c2[E]=(M=16843009*u2^65537*d2^257*f2^16843008*m)<<24|M>>>8,a2[E]=M<<16|M>>>16,h2[E]=M<<8|M>>>24,l2[E]=M,m?(m=f2^x[x[x[u2^f2]]],S^=x[x[S]]):m=S=1}var p2=[0,1,2,4,8,16,32,64,128,27,54],u=u.AES=p.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=4*(1+(this._nRounds=6+r)),o=this._keySchedule=[],n=0;n>>24]<<24|k[a>>>16&255]<<16|k[a>>>8&255]<<8|k[255&a]):(a=k[(a=a<<8|a>>>24)>>>24]<<24|k[a>>>16&255]<<16|k[a>>>8&255]<<8|k[255&a],a^=p2[n/r|0]<<24),o[n]=o[n-r]^a);for(var s=this._invKeySchedule=[],c=0;c>>24]]^a2[k[a>>>16&255]]^h2[k[a>>>8&255]]^l2[k[255&a]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,i2,o2,n2,s2,k)},decryptBlock:function(t,e){var r=t[e+1],r=(t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,c2,a2,h2,l2,r2),t[e+1]);t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,o,n,s,c){for(var a=this._nRounds,h=t[e]^r[0],l=t[e+1]^r[1],f=t[e+2]^r[2],d=t[e+3]^r[3],u=4,p=1;p>>24]^o[l>>>16&255]^n[f>>>8&255]^s[255&d]^r[u++],y=i[l>>>24]^o[f>>>16&255]^n[d>>>8&255]^s[255&h]^r[u++],g=i[f>>>24]^o[d>>>16&255]^n[h>>>8&255]^s[255&l]^r[u++],v=i[d>>>24]^o[h>>>16&255]^n[l>>>8&255]^s[255&f]^r[u++],h=_,l=y,f=g,d=v;_=(c[h>>>24]<<24|c[l>>>16&255]<<16|c[f>>>8&255]<<8|c[255&d])^r[u++],y=(c[l>>>24]<<24|c[f>>>16&255]<<16|c[d>>>8&255]<<8|c[255&h])^r[u++],g=(c[f>>>24]<<24|c[d>>>16&255]<<16|c[h>>>8&255]<<8|c[255&l])^r[u++],v=(c[d>>>24]<<24|c[h>>>16&255]<<16|c[l>>>8&255]<<8|c[255&f])^r[u++];t[e]=_,t[e+1]=y,t[e+2]=g,t[e+3]=v},keySize:8}),P=(w.AES=p._createHelper(u),i),_2=(w=P.lib).WordArray,w=w.BlockCipher,p=P.algo,y2=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],g2=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],v2=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],B2=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],w2=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],k2=p.DES=w.extend({_doReset:function(){for(var t=this._key.words,e=[],r=0;r<56;r++){var i=y2[r]-1;e[r]=t[i>>>5]>>>31-i%32&1}for(var o=this._subKeys=[],n=0;n<16;n++){for(var s=o[n]=[],c=v2[n],r=0;r<24;r++)s[r/6|0]|=e[(g2[r]-1+c)%28]<<31-r%6,s[4+(r/6|0)]|=e[28+(g2[r+24]-1+c)%28]<<31-r%6;s[0]=s[0]<<1|s[0]>>>31;for(r=1;r<7;r++)s[r]=s[r]>>>4*(r-1)+3;s[7]=s[7]<<5|s[7]>>>27}for(var a=this._invSubKeys=[],r=0;r<16;r++)a[r]=o[15-r]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(t,e,r){this._lBlock=t[e],this._rBlock=t[e+1],x2.call(this,4,252645135),x2.call(this,16,65535),b2.call(this,2,858993459),b2.call(this,8,16711935),x2.call(this,1,1431655765);for(var i=0;i<16;i++){for(var o=r[i],n=this._lBlock,s=this._rBlock,c=0,a=0;a<8;a++)c|=B2[a][((s^o[a])&w2[a])>>>0];this._lBlock=s,this._rBlock=n^c}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,x2.call(this,1,1431655765),b2.call(this,8,16711935),b2.call(this,2,858993459),x2.call(this,16,65535),x2.call(this,4,252645135),t[e]=this._lBlock,t[e+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function x2(t,e){e=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=e,this._lBlock^=e<>>t^this._lBlock)&e;this._lBlock^=e,this._rBlock^=e<192.");var e=t.slice(0,2),r=t.length<4?t.slice(0,2):t.slice(2,4),t=t.length<6?t.slice(0,2):t.slice(4,6);this._des1=k2.createEncryptor(_2.create(e)),this._des2=k2.createEncryptor(_2.create(r)),this._des3=k2.createEncryptor(_2.create(t))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2}),P.TripleDES=w._createHelper(p);var u=i,P=u.lib.StreamCipher,w=u.algo,m2=w.RC4=P.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],o=0;o<256;o++)i[o]=o;for(var o=0,n=0;o<256;o++){var s=o%r,s=e[s>>>2]>>>24-s%4*8&255,n=(n+i[o]+s)%256,s=i[o];i[o]=i[n],i[n]=s}this._i=this._j=0},_doProcessBlock:function(t,e){t[e]^=S2.call(this)},keySize:8,ivSize:0});function S2(){for(var t=this._S,e=this._i,r=this._j,i=0,o=0;o<4;o++){var r=(r+t[e=(e+1)%256])%256,n=t[e];t[e]=t[r],t[r]=n,i|=t[(t[e]+t[r])%256]<<24-8*o}return this._i=e,this._j=r,i}function A2(){for(var t=this._X,e=this._C,r=0;r<8;r++)c[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],o=65535&i,n=i>>>16;a[r]=((o*o>>>17)+o*n>>>15)+n*n^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,t[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,t[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,t[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,t[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,t[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,t[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,t[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}function z2(){for(var t=this._X,e=this._C,r=0;r<8;r++)f[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],o=65535&i,n=i>>>16;d[r]=((o*o>>>17)+o*n>>>15)+n*n^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=d[0]+(d[7]<<16|d[7]>>>16)+(d[6]<<16|d[6]>>>16)|0,t[1]=d[1]+(d[0]<<8|d[0]>>>24)+d[7]|0,t[2]=d[2]+(d[1]<<16|d[1]>>>16)+(d[0]<<16|d[0]>>>16)|0,t[3]=d[3]+(d[2]<<8|d[2]>>>24)+d[1]|0,t[4]=d[4]+(d[3]<<16|d[3]>>>16)+(d[2]<<16|d[2]>>>16)|0,t[5]=d[5]+(d[4]<<8|d[4]>>>24)+d[3]|0,t[6]=d[6]+(d[5]<<16|d[5]>>>16)+(d[4]<<16|d[4]>>>16)|0,t[7]=d[7]+(d[6]<<8|d[6]>>>24)+d[5]|0}u.RC4=P._createHelper(m2),w=w.RC4Drop=m2.extend({cfg:m2.cfg.extend({drop:192}),_doReset:function(){m2._doReset.call(this);for(var t=this.cfg.drop;0>>24)|4278255360&(t[r]<<24|t[r]>>>8);for(var i=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],o=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],r=this._b=0;r<4;r++)A2.call(this);for(r=0;r<8;r++)o[r]^=i[r+4&7];if(e){var e=e.words,n=e[0],e=e[1],n=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e=16711935&(e<<8|e>>>24)|4278255360&(e<<24|e>>>8),s=n>>>16|4294901760&e,c=e<<16|65535&n;o[0]^=n,o[1]^=s,o[2]^=e,o[3]^=c,o[4]^=n,o[5]^=s,o[6]^=e,o[7]^=c;for(r=0;r<4;r++)A2.call(this)}},_doProcessBlock:function(t,e){var r=this._X;A2.call(this),n[0]=r[0]^r[5]>>>16^r[3]<<16,n[1]=r[2]^r[7]>>>16^r[5]<<16,n[2]=r[4]^r[1]>>>16^r[7]<<16,n[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)n[i]=16711935&(n[i]<<8|n[i]>>>24)|4278255360&(n[i]<<24|n[i]>>>8),t[e+i]^=n[i]},blockSize:4,ivSize:2}),p.Rabbit=u._createHelper(P),p=(w=i).lib.StreamCipher,u=w.algo,h=[],f=[],d=[],u=u.RabbitLegacy=p.extend({_doReset:function(){for(var t=this._key.words,e=this.cfg.iv,r=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],i=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],o=this._b=0;o<4;o++)z2.call(this);for(o=0;o<8;o++)i[o]^=r[o+4&7];if(e){var t=e.words,e=t[0],t=t[1],e=16711935&(e<<8|e>>>24)|4278255360&(e<<24|e>>>8),t=16711935&(t<<8|t>>>24)|4278255360&(t<<24|t>>>8),n=e>>>16|4294901760&t,s=t<<16|65535&e;i[0]^=e,i[1]^=n,i[2]^=t,i[3]^=s,i[4]^=e,i[5]^=n,i[6]^=t,i[7]^=s;for(o=0;o<4;o++)z2.call(this)}},_doProcessBlock:function(t,e){var r=this._X;z2.call(this),h[0]=r[0]^r[5]>>>16^r[3]<<16,h[1]=r[2]^r[7]>>>16^r[5]<<16,h[2]=r[4]^r[1]>>>16^r[7]<<16,h[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)h[i]=16711935&(h[i]<<8|h[i]>>>24)|4278255360&(h[i]<<24|h[i]>>>8),t[e+i]^=h[i]},blockSize:4,ivSize:2}),w.RabbitLegacy=p._createHelper(u);{w=(P=i).lib.BlockCipher,p=P.algo;const F=16,D2=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],E2=[[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946],[1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055],[3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504],[976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462]];var H2={pbox:[],sbox:[]};function C2(t,e){var r=t.sbox[0][e>>24&255]+t.sbox[1][e>>16&255];return r=(r^=t.sbox[2][e>>8&255])+t.sbox[3][255&e]}function R2(e,t,r){let i=t,o=r,n;for(let t=0;t=a&&(e=0);let r=0,i=0,o=0;for(let t=0;t