├── backend ├── db.db ├── modules │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── image_api.cpython-38.pyc │ │ │ ├── pirate_api.cpython-38.pyc │ │ │ ├── login_manager.cpython-38.pyc │ │ │ ├── password_hash.cpython-38.pyc │ │ │ └── token_manager.cpython-38.pyc │ │ ├── password_hash.py │ │ ├── image_api.py │ │ ├── pirate_api.py │ │ ├── token_manager.py │ │ └── login_manager.py │ ├── config │ │ ├── __init__.py │ │ ├── crypto.txt │ │ ├── config.json │ │ ├── __pycache__ │ │ │ ├── config_manager.cpython-38.pyc │ │ │ └── config_manager.cpython-38.pyc.1477637216048 │ │ └── config_manager.py │ ├── database │ │ ├── __init__.py │ │ ├── db.db │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── database.cpython-38.pyc │ │ │ ├── image_api.cpython-38.pyc │ │ │ ├── pirate_api.cpython-38.pyc │ │ │ └── database_api.cpython-38.pyc │ │ ├── testdb.py │ │ └── database_api.py │ └── __pycache__ │ │ └── main.cpython-38.pyc ├── __pycache__ │ └── main.cpython-38.pyc ├── models │ ├── torrent.py │ ├── __pycache__ │ │ ├── user.cpython-38.pyc │ │ ├── torrent.cpython-38.pyc │ │ └── user_login.cpython-38.pyc │ ├── user_login.py │ └── user.py ├── requirements.txt ├── Dockerfile └── main.py ├── frontend ├── src │ ├── App.css │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── App.js │ ├── Styles │ │ └── style.css │ ├── Components │ │ ├── Torrent.jsx │ │ ├── ProfileBar.jsx │ │ ├── SearchBar.jsx │ │ ├── Navbar.jsx │ │ ├── Torrent_Table.jsx │ │ └── LoginPage.jsx │ ├── logo.svg │ └── serviceWorker.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── index.html │ └── manifest.json ├── Dockerfile └── package.json ├── docker-compose.yml ├── .gitignore ├── .github └── dependabot.yml ├── LICENSE.md └── README.md /backend/db.db: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/modules/config/crypto.txt: -------------------------------------------------------------------------------- 1 | Imfp3BVPcbBK3RvjYdAimhSqQKfTP9by4HETeiM16ic= -------------------------------------------------------------------------------- /backend/modules/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secret" : "123ax44f7gvsw76d32xs6" 3 | } -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.6 2 | 3 | COPY . . 4 | 5 | RUN yarn install 6 | 7 | EXPOSE 3000 8 | 9 | ENTRYPOINT ["yarn", "start"] -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /backend/modules/database/db.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/db.db -------------------------------------------------------------------------------- /backend/__pycache__/main.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/__pycache__/main.cpython-38.pyc -------------------------------------------------------------------------------- /backend/models/torrent.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class Torrent(BaseModel): 4 | name: str 5 | magnet: str 6 | size: str 7 | category: str -------------------------------------------------------------------------------- /backend/models/__pycache__/user.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/models/__pycache__/user.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/__pycache__/main.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/__pycache__/main.cpython-38.pyc -------------------------------------------------------------------------------- /backend/models/__pycache__/torrent.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/models/__pycache__/torrent.cpython-38.pyc -------------------------------------------------------------------------------- /backend/models/__pycache__/user_login.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/models/__pycache__/user_login.cpython-38.pyc -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.70.1 2 | -e git+https://github.com/brandongallagher1999/TPB.git#egg=thepiratebay 3 | aiofiles==0.8.0 4 | uvicorn==0.16.0 5 | gunicorn==20.1.0 6 | jwt==1.3.1 -------------------------------------------------------------------------------- /backend/modules/api/__pycache__/image_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/api/__pycache__/image_api.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/api/__pycache__/pirate_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/api/__pycache__/pirate_api.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/api/__pycache__/login_manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/api/__pycache__/login_manager.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/api/__pycache__/password_hash.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/api/__pycache__/password_hash.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/api/__pycache__/token_manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/api/__pycache__/token_manager.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/database/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/database/__pycache__/database.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/__pycache__/database.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/database/__pycache__/image_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/__pycache__/image_api.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/database/__pycache__/pirate_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/__pycache__/pirate_api.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/config/__pycache__/config_manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/config/__pycache__/config_manager.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/database/__pycache__/database_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/database/__pycache__/database_api.cpython-38.pyc -------------------------------------------------------------------------------- /backend/modules/config/__pycache__/config_manager.cpython-38.pyc.1477637216048: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandongallagher1999/CrypTorrents-React-FastAPI/HEAD/backend/modules/config/__pycache__/config_manager.cpython-38.pyc.1477637216048 -------------------------------------------------------------------------------- /backend/models/user_login.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class UserLogin(BaseModel): 5 | ''' 6 | User login authentication 7 | @username: str 8 | @password: str 9 | ''' 10 | username: str 11 | password: str -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:latest 2 | 3 | COPY . . 4 | 5 | RUN python -m pip install --upgrade pip 6 | RUN pip install -r requirements.txt 7 | 8 | #EXPOSE port 5000 for Uvicorn server 9 | EXPOSE 5000 10 | 11 | ENTRYPOINT ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"] -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /backend/models/user.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from pydantic import BaseModel 3 | from .torrent import Torrent 4 | from typing import List 5 | 6 | class User(BaseModel): 7 | ''' 8 | User stored in the database (not for authentication.) 9 | ''' 10 | username: str 11 | saved_torrents: List[Torrent] 12 | logged_in: bool -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | 4 | services: 5 | backend: 6 | build: 7 | context: "./backend" 8 | dockerfile: Dockerfile 9 | ports: 10 | - "5000:5000" 11 | 12 | frontend: 13 | stdin_open: true 14 | build: 15 | context: "./frontend" 16 | dockerfile: Dockerfile 17 | ports: 18 | - "3000:3000" 19 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /frontend/node_modules 5 | /.pnp 6 | .pnp.js 7 | /backend/src 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | .vscode 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React App 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import {BrowserRouter, Route, Routes} from 'react-router-dom'; 4 | import Navbar from './Components/Navbar'; 5 | import SearchBar from './Components/SearchBar'; 6 | import TorrentTable from './Components/Torrent_Table'; 7 | 8 | function App() { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | }/> 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | export default App; -------------------------------------------------------------------------------- /backend/modules/api/password_hash.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | class PasswordHash(): 4 | ''' 5 | Salts and hashes a given password 6 | @returns -> str: a salted and hashed password. 7 | ''' 8 | 9 | def __init__(self, password: str): 10 | self.password = password 11 | self.salt = "f54ga" 12 | 13 | def hash(self) -> str: 14 | ''' 15 | @returns -> str: Hashed password 16 | ''' 17 | return hashlib.md5(str(f"{self.salt}{self.password}{self.salt}").encode("utf-8")).hexdigest() 18 | 19 | def __str__(self) -> str: 20 | ''' 21 | Allows for p = PasswordHash("password") to auto return the hashed value 22 | ''' 23 | return self.hash() -------------------------------------------------------------------------------- /frontend/src/Styles/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .pirate_icon 4 | { 5 | background-image: url("https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F4%2F4a%2FPirate_icon.gif&f=1"); 6 | width: 75px; 7 | height: 75px; 8 | background-size: contain; 9 | position: relative; 10 | } 11 | 12 | .pirate_icon:hover 13 | { 14 | opacity: 0.3; 15 | cursor: pointer; 16 | } 17 | 18 | .middle_div 19 | { 20 | position: absolute; 21 | top: 10%; 22 | } 23 | 24 | h4 25 | { 26 | color:yellow; 27 | } 28 | 29 | th 30 | { 31 | color:yellow; 32 | } 33 | 34 | profile_box 35 | { 36 | width: 75%; 37 | height: 20%; 38 | top: 100px; 39 | position: relative; 40 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/backend" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 99 8 | ignore: 9 | - dependency-name: uvicorn 10 | versions: 11 | - 0.13.4 12 | - dependency-name: aiofiles 13 | versions: 14 | - 0.6.0 15 | - package-ecosystem: npm 16 | directory: "/frontend" 17 | schedule: 18 | interval: daily 19 | open-pull-requests-limit: 99 20 | ignore: 21 | - dependency-name: "@testing-library/user-event" 22 | versions: 23 | - 13.0.10 24 | - 13.0.13 25 | - 13.0.7 26 | - 13.1.2 27 | - dependency-name: react-dom 28 | versions: 29 | - 16.14.0 30 | - dependency-name: react-scripts 31 | versions: 32 | - 4.0.3 33 | - dependency-name: "@testing-library/jest-dom" 34 | versions: 35 | - 5.11.9 36 | - dependency-name: jquery 37 | versions: 38 | - 3.6.0 39 | -------------------------------------------------------------------------------- /backend/modules/api/image_api.py: -------------------------------------------------------------------------------- 1 | from requests import Session 2 | import json 3 | 4 | class ImageAPI(): 5 | ''' 6 | This class interacts with DuckDuckGo image API to retrive certain images via URL. 7 | ''' 8 | def __init__(self): 9 | #To make HTTP Requests. 10 | self.s = Session() 11 | 12 | def get_image(self, name: str) -> str: 13 | 14 | ''' 15 | Gets an image URL from DuckDuckGo API 16 | 17 | @param {str} name: name of the image to lookup 18 | @return {str} url of the thing to look up (movie, game, book, etc) 19 | ''' 20 | 21 | headers: dict = { 22 | "Accept" : "application/json", 23 | } 24 | r = self.s.get( 25 | url = f'http://api.duckduckgo.com/?format=json&pretty=1&q={name}', 26 | verify = False, 27 | headers = headers 28 | ) 29 | 30 | d: dict = json.loads(json.dumps(r.json())) 31 | return str(d["Image"]) -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.1", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "bulma": "^0.9.3", 10 | "jquery": "^3.5.1", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-router-dom": "^6.2.1", 14 | "react-scripts": "5.0.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/Components/Torrent.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Torrent extends React.Component 4 | { 5 | 6 | displayMagnet() 7 | { 8 | /* 9 | Displays the magnet box that is Copyable. 10 | */ 11 | prompt("Copy this with CTRL + C", this.props.magnet); 12 | } 13 | 14 | //Props = name, magnet, size, category 15 | render() 16 | { 17 | const {name, image} = this.props; 18 | const divStyle = { 19 | backgroundImage : `url(${image})`, 20 | width: "200px", 21 | height: "200px" 22 | }; 23 | 24 | return( 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | {name} 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/Components/ProfileBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | export default class ProfileBar extends React.Component 5 | { 6 | 7 | constructor(props) 8 | { 9 | super(props); 10 | } 11 | 12 | componentDidMount() 13 | { 14 | fetch(`api/get/user/`, { // get the users profile info 15 | method : "GET", 16 | credentials : "same-origin" 17 | }) 18 | .then(response => response.json()) //then is called with response as the argument then we get it's .json 19 | .then(data => { //this .then() function takes the last functions response as an argument 20 | this.setState({ 21 | profile : data 22 | }) 23 | }); 24 | } 25 | 26 | render() 27 | { 28 | return( 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | {"to fill out"} 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | ); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /frontend/src/Components/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import $ from 'jquery'; 3 | 4 | export default class SearchBar extends React.Component 5 | { 6 | constructor(props) 7 | { 8 | super(props); 9 | this.state = { 10 | inputStyle : { 11 | width : "600px", 12 | height : "50px" 13 | }, 14 | }; 15 | } 16 | 17 | keyPress(event) 18 | { 19 | if (event.key === "Enter") 20 | { 21 | document.getElementById("my-btn-search").click(); 22 | } 23 | } 24 | 25 | go_to_url() 26 | { 27 | let url = "/torrents/" + $("#my-form-query").val(); 28 | document.location.href = url; 29 | } 30 | 31 | render() 32 | { 33 | return( 34 |
35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 | ); 43 | } 44 | } -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from starlette.middleware.cors import CORSMiddleware 3 | import json 4 | import os 5 | from starlette.requests import Request 6 | from fastapi import FastAPI, Body 7 | from starlette.templating import Jinja2Templates 8 | from starlette.requests import Request 9 | from starlette.staticfiles import StaticFiles 10 | from starlette import responses 11 | sys.path.append("./modules/database/") 12 | sys.path.append("./modules/api/") 13 | sys.path.append("./modules/config/") 14 | from database_api import Database_API 15 | from pirate_api import Pirate_API 16 | from login_manager import LoginManager 17 | from models.user_login import UserLogin 18 | from models.user import User 19 | from starlette import responses 20 | from fastapi.encoders import jsonable_encoder 21 | import sqlite3 22 | 23 | 24 | 25 | app = FastAPI() 26 | pirate_api = Pirate_API() 27 | database = Database_API() 28 | 29 | origins = ["http://localhost:3000"] 30 | 31 | # TODO: Change origin to real domain to reject Ajax requests from elsewhere 32 | app.add_middleware(CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]) 33 | 34 | 35 | 36 | @app.get("/api/torrents/") 37 | async def get_torrent(request: Request, torrent: str = "") -> responses.JSONResponse: 38 | ''' 39 | Gets the top 3 torrents as a JSON 40 | 41 | :torrent: (str) -> The name of the torrent whether it be a movie, game, book, etc. 42 | :return: (JSON) JSON of top 3 torrents 43 | ''' 44 | 45 | return responses.JSONResponse(pirate_api.get_torrents(torrent)) -------------------------------------------------------------------------------- /backend/modules/config/config_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class ConfigManager(): 4 | ''' 5 | Manages system variables to config.json 6 | ''' 7 | 8 | def __init__(self): 9 | self.config_dict: dict = {} #Empty dictionary 10 | self.file_name: str = "./modules/config/config.json" 11 | 12 | def set_config(self, key: str, value: str) -> None: 13 | ''' 14 | Set a configuration value with a given key and value 15 | 16 | @key: str -> the key 17 | @value: str -> value of the key 18 | @returns: None 19 | ''' 20 | 21 | self.config_dict = json.loads(open(self.file_name, "r").read()) 22 | self.config_dict[key] = value 23 | with open(self.file_name, "w") as f: 24 | f.writelines(json.dumps(self.config_dict, indent=4)) 25 | 26 | def set_crypto(self, key: bytes) -> None: 27 | 28 | with open("./modules/config/crypto.txt", "w") as f: 29 | f.write(key.decode("utf-8")) 30 | 31 | def get_crypto(self) -> bytes: 32 | 33 | # with open("./modules/config/crypto.txt", "r") as f: 34 | # print(f.read()) 35 | 36 | 37 | with open("./modules/config/crypto.txt", "r") as f: 38 | return f.readline().encode("utf-8") 39 | 40 | def get_config(self, key: str) -> str: 41 | ''' 42 | Returns value of config variable based on key 43 | 44 | @key: str -> the key to find in .json file 45 | @returns: str -> the value of environment variable 46 | ''' 47 | 48 | self.config_dict = json.loads(open(self.file_name, "rb").read()) 49 | return self.config_dict[key] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © [2020] [Brandon Gallagher] 2 | 3 | This is anti-capitalist software, released for free use by individuals and organizations that do not operate by capitalist principles. 4 | 5 | Permission is hereby granted, free of charge, to any person or organization (the "User") obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | 1. The above copyright notice and this permission notice shall be included in all copies of the Software. 8 | 2. The User is one of the following: 9 | a. An individual person 10 | b. An organization of people that does not seek profit 11 | c. An organization of people that seeks shared profit for all its members and does not exploit the labor of non-members 12 | 3. If the User is an organization with owners, then all owners are workers and all workers are owners with equal share. 13 | 4. If the User is an organization, then the User is not law enforcement or military, or working under either. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Skull Icon](https://cdn3.iconfinder.com/data/icons/universal-signs-symbols/128/pirate-sword-skull2-512.png) 2 | 3 | Version: Release v4.0 4 | 5 | ## Github project URL 6 | https://github.com/brandongallagher1999/CrypTorrents---FastAPI/ 7 | 8 | ## Hosted Website URL 9 | http://cryptorrents.herokuapp.com/ 10 | - **The website could currently be down to my Heroku Free-Tier running out of dyno hours. If this is the case, please run it using docker-compose.** 11 | - *Accessing website may be slow initially due to the Docker Container spinning up.* 12 | 13 | 14 | ## Contributors 15 | 1. **Brandon Gallagher** 16 | - Roles: Back-end / Front-End / Database 17 | - Email: brandonegallagher@gmail.com 18 | - [Github Profile](https://github.com/brandongallagher1999) 19 | 20 | ## Description 21 | - Front-End is built in React 22 | - Back-End is built in FastAPI 23 | - Uses ThePirateBay REST API to fetch torrent information based on a query, parses it, and displays the top 3 results 24 | sorted by Seeders, and displays them beside a relevant picture from the DuckDuckGo API. 25 | 26 | ## React 27 | - https://reactjs.org/ 28 | 29 | ## Fast API 30 | - https://fastapi.tiangolo.com/features/ 31 | 32 | 33 | # How to run 34 | ## Docker 35 | 1. Download Docker for Desktop (https://www.docker.com/products/docker-desktop) 36 | 37 | ## Container 38 | 1. Go into root folder and run 39 | ``` 40 | docker-compose build 41 | docker-compose up -d 42 | ``` 43 | 44 | ## Opening Web App 45 | 1. Go into any browser, preferably Google Chrome 46 | 2. In the URL bar, type "localhost:3000" 47 | 48 | 49 | # Upcoming Changes 50 | - Login page completed, working on having a profile page load stored user's content. 51 | -------------------------------------------------------------------------------- /backend/modules/api/pirate_api.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append(".") 3 | 4 | from tpb import TPB 5 | from tpb import ORDERS 6 | from typing import List 7 | from image_api import ImageAPI 8 | from config_manager import ConfigManager 9 | 10 | class Pirate_API(): 11 | ''' 12 | This class allows my endpoints to interact with the database as well as retrieve 13 | information from ThePirateBay's database. 14 | ''' 15 | 16 | def __init__(self): 17 | 18 | self.iAPI = ImageAPI() 19 | self.config = ConfigManager() 20 | self.website = TPB("https://thepiratebay.org/") #Base URL for ThePirateBay 21 | 22 | def get_none(self) -> List[dict]: 23 | ''' 24 | Returns an empty torrent object 25 | 26 | @returns -> None 27 | ''' 28 | 29 | return [{ 30 | "name" : "", 31 | "magnet" : "", 32 | "image" : "" 33 | }] 34 | 35 | def get_torrents(self, req: str) -> List[dict]: 36 | ''' 37 | Gets all torrents relative to the search query. 38 | 39 | @param {str} req: The search query to be made to ThePirateBay 40 | @returns {List[dict]} 3 top torrent results sorted by seeders. 41 | ''' 42 | 43 | counter: int = 0 44 | obj: List[dict]= [] 45 | 46 | button_id: str = "pirate_button_" 47 | button_count: int = 1 48 | 49 | for torrent in self.website.search(req).order(ORDERS.SEEDERS.DES): 50 | if counter <= 2: 51 | obj.append( 52 | { 53 | "name" : torrent.title, 54 | "magnet" : torrent.magnet_link, 55 | "image_url" : self.iAPI.get_image(req), 56 | "button_id" : button_id + str(button_count), 57 | } 58 | ) 59 | counter += 1 60 | button_count += 1 61 | return obj -------------------------------------------------------------------------------- /frontend/src/Components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | 5 | export default class NavBar extends React.Component 6 | { 7 | constructor(props) 8 | { 9 | super(props); 10 | }; 11 | 12 | render() 13 | { 14 | return( 15 | 47 | 48 | ); 49 | }; 50 | } -------------------------------------------------------------------------------- /frontend/src/Components/Torrent_Table.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Torrent from './Torrent'; 3 | import '../Styles/style.css' 4 | 5 | export default class TorrentTable extends React.Component 6 | { 7 | constructor(props) 8 | { 9 | super(props); 10 | this.state = { 11 | torrents : [] 12 | }; 13 | } 14 | 15 | componentDidMount() 16 | { 17 | console.log("Called!"); 18 | let torrent_string = window.location.href.split("/")[4]; 19 | console.log(window.location.href); 20 | 21 | fetch(`http://localhost:5000/api/torrents/?torrent=${torrent_string}`, { 22 | headers : { 23 | 'Content-Type': 'application/json', 24 | 'Accept': 'application/json', 25 | } 26 | }) 27 | .then(res => res.json()) 28 | .then(data => { 29 | this.setState({ 30 | torrents : data 31 | }) 32 | }); 33 | 34 | } 35 | 36 | render() 37 | { 38 | 39 | return( 40 | 41 | 42 | 43 | 46 | 47 | 50 | 51 | 54 | 55 | 56 | 57 | { 58 | this.state.torrents.map(torrent => { 59 | return 60 | }) 61 | } 62 | 63 |
44 | 45 | 48 | Torrent Name 49 | 52 | Magnet 53 |
64 | ); 65 | } 66 | } -------------------------------------------------------------------------------- /backend/modules/api/token_manager.py: -------------------------------------------------------------------------------- 1 | 2 | #from ..config.config_manager import ConfigManager 3 | import jwt 4 | import datetime 5 | from models.user_login import UserLogin 6 | from config_manager import ConfigManager 7 | from cryptography.fernet import Fernet 8 | 9 | class TokenManager(): 10 | ''' 11 | Manages JSON web tokens and gives a token response back based on given user 12 | ''' 13 | 14 | def __init__(self): 15 | 16 | self.config = ConfigManager() 17 | self.key = Fernet.generate_key() 18 | #self.config.set_crypto(self.key) 19 | self.frn: Fernet = Fernet(self.config.get_crypto()) 20 | 21 | def get_unencrypted_token(self, t: bytes) -> bytes: 22 | ''' 23 | Unencrypts the given JWT 24 | 25 | @token: (bytes)-> JWT Token 26 | @returns: (bytes) -> A encrypted token. 27 | ''' 28 | 29 | return self.frn.decrypt(t) 30 | 31 | def get_encrypted_token(self, user: UserLogin) -> bytes: 32 | ''' 33 | Gives a JWT response encoded. 34 | 35 | @user: UserLogin -> username and password from the login form in front end 36 | @returns: str -> A JWT token 37 | ''' 38 | 39 | token: str = jwt.encode( #This token is to be given to the user 40 | { 41 | "username" : user.username, 42 | "password" : user.password, 43 | "exp" : datetime.datetime.utcnow() + datetime.timedelta(days=1) 44 | }, 45 | self.config.get_config("secret"), 46 | algorithm="HS256" 47 | ) 48 | 49 | return self.frn.encrypt(token) #encrypting the login JWT 50 | 51 | def get_payload(self, token: bytes) -> dict: 52 | ''' 53 | Gets the payload data from a given token, decoded using a secret. 54 | 55 | @token: str -> the JWT 56 | @returns: dict -> The user's information. 57 | ''' 58 | 59 | payload: dict = jwt.decode( 60 | token, 61 | self.config.get_config("secret"), 62 | algorithms=["HS256"] 63 | ) 64 | return payload -------------------------------------------------------------------------------- /backend/modules/database/testdb.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | 4 | 5 | 6 | def main(): 7 | 8 | connection = sqlite3.connect("db.db") 9 | c = connection.cursor() 10 | 11 | 12 | # User login model to be stored in database 13 | 14 | # c.execute(''' 15 | # CREATE TABLE IF NOT EXISTS `user_login` ( 16 | # `id` INT AUTO_INCREMENT NOT NULL, 17 | # `username` VARCHAR(45) NOT NULL, 18 | # `password` VARCHAR(100) NULL, 19 | # PRIMARY KEY (`id`)); 20 | # ''') 21 | 22 | 23 | #Creating the user table related to what the model wants to see --> for later relationship join 24 | 25 | # c.execute(''' 26 | # CREATE TABLE IF NOT EXISTS `user` ( 27 | # `username` VARCHAR(25) NOT NULL, 28 | # `id` INT NOT NULL, 29 | # `logged_in` BIT NOT NULL DEFAULT 0, 30 | # PRIMARY KEY (`id`), 31 | # CONSTRAINT `id` 32 | # FOREIGN KEY (`id`) 33 | # REFERENCES `user_login` (`id`) 34 | # ON DELETE NO ACTION 35 | # ON UPDATE NO ACTION); 36 | # ''') 37 | 38 | 39 | #Creating the torrent table 40 | 41 | # c.execute(''' 42 | 43 | # CREATE TABLE IF NOT EXISTS `torrent` ( 44 | # `name` VARCHAR(100) NOT NULL, 45 | # `magnet` VARCHAR(255) NOT NULL, 46 | # `size` VARCHAR(45) NOT NULL DEFAULT 0, 47 | # `category` VARCHAR(45) NOT NULL, 48 | # `user_id` INT NOT NULL, 49 | # CONSTRAINT `user_id` 50 | # FOREIGN KEY (`user_id`) 51 | # REFERENCES `user` (`id`) 52 | # ON DELETE NO ACTION 53 | # ON UPDATE NO ACTION); 54 | # ''') 55 | 56 | c.execute(''' 57 | 58 | INSERT INTO 'torrent' VALUES 59 | ( 60 | 'name', 61 | 'magnet', 62 | 'size', 63 | 'category', 64 | 1 65 | ); 66 | ''' 67 | ) 68 | connection.commit() 69 | 70 | for row in c.execute("SELECT * FROM `torrent`;"): 71 | print(row) 72 | 73 | 74 | 75 | 76 | 77 | 78 | connection.close() 79 | 80 | main() 81 | 82 | 83 | -------------------------------------------------------------------------------- /backend/modules/api/login_manager.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("./database") 3 | from database_api import Database_API 4 | from models.user import User 5 | from models.user_login import UserLogin 6 | from password_hash import PasswordHash 7 | from token_manager import TokenManager 8 | 9 | class LoginManager(): 10 | ''' 11 | Manages login sessions for the given user. 12 | ''' 13 | 14 | def __init__(self): 15 | self.database = Database_API() 16 | self.token_mg = TokenManager() 17 | 18 | def logout(self, user: User) -> None: 19 | ''' 20 | Logs user out. 21 | 22 | @returns -> None. 23 | ''' 24 | self.database.logout() 25 | 26 | def get_profile(self, token: str) -> User: 27 | ''' 28 | Gets the user profile dictionary containing all public info like saved torrents, etc. 29 | 30 | :token: (str) encrypted JWT 31 | :return: (dict) user profile with saved torrents, and public updatable data 32 | ''' 33 | try: 34 | 35 | token = bytes(token[2 : len(token)-1], encoding="utf-8") #properly converting the cookie token string into bytes for decrypting 36 | 37 | unencrypted_token: str = self.token_mg.get_unencrypted_token(token) #unencrypt the given JWT 38 | temp_user_info: dict = self.token_mg.get_payload(unencrypted_token) #Get the JWT payload data (dictionary) 39 | 40 | userLogin_obj: UserLogin = UserLogin(username= temp_user_info["username"], password=temp_user_info["password"]) 41 | 42 | user_obj: User = self.database.get_user(userLogin_obj) #Retrieve info from DB 43 | 44 | if (user_obj == None): 45 | return { "Message" : 401} 46 | 47 | else: 48 | return user_obj 49 | 50 | except: 51 | return None 52 | 53 | 54 | def create_session(self, user: UserLogin) -> bytes: 55 | ''' 56 | Gets a user session token 57 | @returns: Token (str): token to be given to user accessing website (for their cookies) 58 | ''' 59 | 60 | token: bytes = self.token_mg.get_encrypted_token(user) 61 | return token -------------------------------------------------------------------------------- /frontend/src/Components/LoginPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class LoginPage extends React.Component 4 | { 5 | constructor(props) 6 | { 7 | super(props); 8 | this.state = {}; 9 | }; 10 | 11 | async submitLogin() 12 | { 13 | let username = document.getElementById("username_input1").value; 14 | let password = document.getElementById("password_input1").value; 15 | 16 | let data = { 17 | "username": username, 18 | "password": password 19 | }; 20 | 21 | let url = "/sessions/login/"; 22 | const req = await fetch(url, { 23 | method: "POST", 24 | headers: { 25 | 'Content-Type': "application/json", 26 | "Accept" : "application/json", 27 | }, 28 | body: JSON.stringify(data) 29 | }); 30 | }; 31 | 32 | render() 33 | { 34 | const divStyle = { 35 | top : "25%", 36 | position: "absolute", 37 | left: "30%", 38 | width: "650px", 39 | height: "auto", 40 | backgroundColor : "whitesmoke", 41 | borderRadius: "25px", 42 | }; 43 | 44 | return( 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | If you forgot your password click here 54 |
55 |
56 |
57 | ); 58 | }; 59 | 60 | } -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/modules/database/database_api.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import hashlib 3 | import sys 4 | sys.path.append("..") 5 | from password_hash import PasswordHash 6 | from models.user import User 7 | from models.torrent import Torrent 8 | from models.user_login import UserLogin 9 | from typing import List 10 | 11 | class Database_API(): 12 | ''' 13 | Interacts with the SqlLite3 database through sqllite3 library. 14 | 15 | :returns: None 16 | ''' 17 | def __init__(self): 18 | self.connection = sqlite3.connect("db.db") 19 | self.c = self.connection.cursor() 20 | 21 | def create_table(self) -> None: 22 | self.c.execute(''' 23 | CREATE TABLE user( 24 | username TEXT NOT NULL, 25 | password TEXT NOT NULL, 26 | primary key(username) 27 | ) 28 | ''') 29 | 30 | def get_user(self, user: UserLogin) -> User: 31 | ''' 32 | Gets a single user based on their login info 33 | 34 | @returns: User -> a single user 35 | ''' 36 | 37 | temp_user: User = User() 38 | if (self.user_exists(user)): #Check to see if the user actually exists in the database with proper user/password. 39 | 40 | #User model contains relevant user information, part 1 of joining the user info and saved_torrents info 41 | for row in self.c.execute(''' 42 | SELECT username, logged_in 43 | FROM user 44 | WHERE username = {} 45 | '''.format(user.username)): 46 | temp_user.username = row[0] 47 | temp_user.logged_in = row[2] 48 | 49 | #All the torrents that this user has saved. 50 | for row in self.c.execute(''' 51 | SELECT * from 'torrent' 52 | WHERE user_id = {} 53 | '''.format(user.username)): 54 | temp_user.saved_torrents.append(Torrent(name=row[0], magnet=row[1], size=row[2], category=row[3])) #Append to User Model's list of torrents. 55 | 56 | return temp_user 57 | 58 | else: #if they don't exist, return None 59 | return None 60 | 61 | 62 | def get_users(self) -> List[User]: 63 | ''' 64 | Gets all users from the database except their passwords. 65 | 66 | @Returns: List[User] -> All users without password. 67 | ''' 68 | users: List[User] = [] 69 | for row in self.c.execute("SELECT username, saved_torrents, logged_in FROM users"): 70 | temp = User() 71 | temp.username = row[0] 72 | temp.password = "" 73 | temp.saved_torrents = row[1] 74 | temp.logged_in = row[2] 75 | users.append(User) #Append temp object to the list 76 | 77 | return users 78 | 79 | def user_exists(self, user: UserLogin): 80 | count: int = 0 81 | hashed_passw = PasswordHash(user.password) 82 | 83 | for row in self.c.execute(''' 84 | SELECT * from user WHERE username = '{}' AND password = {} 85 | '''.format(user.username, hashed_passw)): 86 | count += 1 87 | 88 | if (count == 1): 89 | return True 90 | else: 91 | return False 92 | 93 | def set_logstatus(self, status: str, username: str) -> None: 94 | self.c.execute(''' 95 | UPDATE user 96 | SET logged_in = {} 97 | WHERE username = {} 98 | '''.format(status, username)) 99 | 100 | def set_logout(self, user: User) -> None: 101 | self.set_logstatus("FALSE", user.username) 102 | 103 | def set_login(self, user: User) -> None: 104 | self.set_logstatus("TRUE", user.username) 105 | 106 | def check_username_exists(self, user: User) -> bool: 107 | 108 | count: int = 0 109 | 110 | for row in self.c.execute(''' 111 | SELECT * from user WHERE username = '{}' 112 | '''.format(user.username)): 113 | count += 1 114 | 115 | # See if at least one row exists if not then return false 116 | if (count > 0): 117 | return True 118 | else: 119 | return False 120 | 121 | 122 | #def logout(self, user: User) -> None: 123 | 124 | def add_user(self, username: str, passw: str): 125 | ''' 126 | Inserts a new user into the user table. 127 | 128 | @username: str -> Username of the user 129 | @passw: str -> Password of the user 130 | 131 | @returns -> None 132 | ''' 133 | 134 | if (self.check_username_exists(username) == False): #If the user doesn't exist, then add them 135 | self.c.execute(''' 136 | INSERT INTO user VALUES ( 137 | '{}', 138 | '{}' 139 | ) 140 | '''.format(username, PasswordHash(passw))) #Add the username, and a salted --> hashed password. 141 | 142 | #Save the database changes 143 | self.connection.commit() 144 | 145 | def close_db(self): 146 | self.connection.close() -------------------------------------------------------------------------------- /frontend/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | --------------------------------------------------------------------------------