├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── components ├── Account │ └── index.js ├── App │ ├── index.css │ └── index.js ├── Home │ └── index.js ├── Landing │ └── index.js ├── Navigation │ └── index.js ├── PasswordChange │ └── index.js ├── PasswordForget │ └── index.js ├── Session │ ├── withAuthentication.js │ └── withAuthorization.js ├── SignIn │ └── index.js ├── SignOut │ └── index.js └── SignUp │ └── index.js ├── constants └── routes.js ├── firebase ├── auth.js ├── db.js ├── firebase.js └── index.js ├── index.css ├── index.js └── registerServiceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-firebase-authentication 2 | 3 | [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) 4 | 5 | * Found in [Taming the State in React](https://roadtoreact.com/course-details?courseId=TAMING_THE_STATE) 6 | * [Live](https://react-firebase-authentication.wieruch.com/) 7 | 8 | ## Features 9 | 10 | * uses: 11 | * only React (create-react-app) 12 | * firebase 4.3.1 13 | * react-router 4.2.0 14 | * no Redux/MobX 15 | * features: 16 | * Sign In 17 | * Sign Up 18 | * Sign Out 19 | * Password Forget 20 | * Password Change 21 | * Protected Routes with Authorization 22 | * Database: Users 23 | 24 | ## Installation 25 | 26 | * `git clone git@github.com:rwieruch/react-firebase-authentication.git` 27 | * `cd react-firebase-authentication` 28 | * `npm install` 29 | * `npm start` 30 | * visit http://localhost:3000/ 31 | * Use your own Firebase Credentials 32 | 33 | ### Use your own Firebase Credentials 34 | 35 | * visit https://firebase.google.com/ and create a Firebase App 36 | * copy and paste your Credentials from your Firebase App into src/firebase/firebase.js 37 | * activate Email/Password Sign-In Method in your Firebase App 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-firebase-authentication", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "firebase": "^4.3.1", 7 | "prop-types": "^15.5.10", 8 | "react": "^16.1.1", 9 | "react-dom": "^16.1.1", 10 | "react-router-dom": "^4.2.2", 11 | "react-scripts": "1.0.17" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/react-firebase-authentication-chat/05db06ccbc8a3ecdd0421ffc4afaca9496953694/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Account/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { PasswordForgetForm } from '../PasswordForget'; 5 | import PasswordChangeForm from '../PasswordChange'; 6 | import withAuthorization from '../Session/withAuthorization'; 7 | 8 | const AccountPage = (props, { authUser }) => 9 |
10 |

Account: {authUser.email}

11 | 12 | 13 |
14 | 15 | AccountPage.contextTypes = { 16 | authUser: PropTypes.object, 17 | }; 18 | 19 | const authCondition = (authUser) => !!authUser; 20 | 21 | export default withAuthorization(authCondition)(AccountPage); -------------------------------------------------------------------------------- /src/components/App/index.css: -------------------------------------------------------------------------------- 1 | .app { 2 | margin: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | BrowserRouter as Router, 4 | Route, 5 | } from 'react-router-dom'; 6 | 7 | import Navigation from '../Navigation'; 8 | import LandingPage from '../Landing'; 9 | import SignUpPage from '../SignUp'; 10 | import SignInPage from '../SignIn'; 11 | import PasswordForgetPage from '../PasswordForget'; 12 | import HomePage from '../Home'; 13 | import AccountPage from '../Account'; 14 | import withAuthentication from '../Session/withAuthentication'; 15 | import * as routes from '../../constants/routes'; 16 | 17 | import './index.css'; 18 | 19 | const App = () => 20 | 21 |
22 | 23 | 24 |
25 | 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | 33 |
34 | 35 | Found in Taming the State in React | Star the Repository | Receive a Developer's Newsletter 36 |
37 |
38 | 39 | export default withAuthentication(App); -------------------------------------------------------------------------------- /src/components/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import withAuthorization from '../Session/withAuthorization'; 5 | import { db } from '../../firebase'; 6 | 7 | class HomePage extends Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = { 12 | value: '', 13 | messages: [], 14 | users: {}, 15 | }; 16 | 17 | this.onAddMessage = this.onAddMessage.bind(this); 18 | this.onChangeMessage = this.onChangeMessage.bind(this); 19 | } 20 | 21 | componentWillMount() { 22 | db.onceGetUsers().then(snapshot => 23 | this.setState(() => ({ users: snapshot.val() })) 24 | ); 25 | 26 | db.onMessageAdded((snapshot) => { 27 | this.setState(prevState => ({ 28 | messages: [ snapshot.val(), ...prevState.messages ], 29 | })); 30 | }); 31 | } 32 | 33 | onChangeMessage(event) { 34 | const { value } = event.target; 35 | this.setState(() => ({ value })); 36 | } 37 | 38 | onAddMessage(event) { 39 | event.preventDefault(); 40 | 41 | const { authUser } = this.context; 42 | const { value } = this.state; 43 | 44 | db.doCreateMessage(authUser.uid, value); 45 | 46 | this.setState(() => ({ value: '' })); 47 | } 48 | 49 | render() { 50 | const { 51 | messages, 52 | users, 53 | value, 54 | } = this.state; 55 | 56 | return ( 57 |
58 | 67 | 68 |
69 | 74 | 75 |
76 |
77 | ); 78 | } 79 | } 80 | 81 | const Message = ({ message, user }) => { 82 | const messenger = user 83 | ? `${user.username}:` 84 | : '???'; 85 | 86 | return
  • {messenger} {message.text}
  • ; 87 | } 88 | 89 | HomePage.contextTypes = { 90 | authUser: PropTypes.object, 91 | }; 92 | 93 | const authCondition = (authUser) => !!authUser; 94 | 95 | export default withAuthorization(authCondition)(HomePage); -------------------------------------------------------------------------------- /src/components/Landing/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LandingPage = () => 4 |
    5 |

    Landing

    6 |

    The Landing Page is open to everyone, even though the user isn't signed in.

    7 |
    8 | 9 | export default LandingPage; 10 | -------------------------------------------------------------------------------- /src/components/Navigation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | import SignOutButton from '../SignOut'; 6 | import * as routes from '../../constants/routes'; 7 | 8 | const Navigation = (props, { authUser }) => 9 |
    10 | { authUser 11 | ? 12 | : 13 | } 14 |
    15 | 16 | Navigation.contextTypes = { 17 | authUser: PropTypes.object, 18 | }; 19 | 20 | const NavigationAuth = () => 21 | 27 | 28 | const NavigationNonAuth = () => 29 | 33 | 34 | export default Navigation; 35 | -------------------------------------------------------------------------------- /src/components/PasswordChange/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { auth } from '../../firebase'; 4 | 5 | const updateByPropertyName = (propertyName, value) => () => ({ 6 | [propertyName]: value, 7 | }); 8 | 9 | const INITIAL_STATE = { 10 | passwordOne: '', 11 | passwordTwo: '', 12 | error: null, 13 | }; 14 | 15 | class PasswordChangeForm extends Component { 16 | constructor(props) { 17 | super(props); 18 | 19 | this.state = { ...INITIAL_STATE }; 20 | } 21 | 22 | onSubmit = (event) => { 23 | const { passwordOne } = this.state; 24 | 25 | auth.doPasswordUpdate(passwordOne) 26 | .then(() => { 27 | this.setState(() => ({ ...INITIAL_STATE })); 28 | }) 29 | .catch(error => { 30 | this.setState(updateByPropertyName('error', error)); 31 | }); 32 | 33 | event.preventDefault(); 34 | } 35 | 36 | render() { 37 | const { 38 | passwordOne, 39 | passwordTwo, 40 | error, 41 | } = this.state; 42 | 43 | const isInvalid = 44 | passwordOne !== passwordTwo || 45 | passwordOne === ''; 46 | 47 | return ( 48 |
    49 | this.setState(updateByPropertyName('passwordOne', event.target.value))} 52 | type="password" 53 | placeholder="New Password" 54 | /> 55 | this.setState(updateByPropertyName('passwordTwo', event.target.value))} 58 | type="password" 59 | placeholder="Confirm New Password" 60 | /> 61 | 64 | 65 | { error &&

    {error.message}

    } 66 |
    67 | ); 68 | } 69 | } 70 | 71 | export default PasswordChangeForm; -------------------------------------------------------------------------------- /src/components/PasswordForget/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import { auth } from '../../firebase'; 5 | import * as routes from '../../constants/routes'; 6 | 7 | const PasswordForgetPage = () => 8 |
    9 |

    PasswordForget

    10 | 11 |
    12 | 13 | const updateByPropertyName = (propertyName, value) => () => ({ 14 | [propertyName]: value, 15 | }); 16 | 17 | const INITIAL_STATE = { 18 | email: '', 19 | error: null, 20 | }; 21 | 22 | class PasswordForgetForm extends Component { 23 | constructor(props) { 24 | super(props); 25 | 26 | this.state = { ...INITIAL_STATE }; 27 | } 28 | 29 | onSubmit = (event) => { 30 | const { email } = this.state; 31 | 32 | auth.doPasswordReset(email) 33 | .then(() => { 34 | this.setState(() => ({ ...INITIAL_STATE })); 35 | }) 36 | .catch(error => { 37 | this.setState(updateByPropertyName('error', error)); 38 | }); 39 | 40 | event.preventDefault(); 41 | } 42 | 43 | render() { 44 | const { 45 | email, 46 | error, 47 | } = this.state; 48 | 49 | const isInvalid = email === ''; 50 | 51 | return ( 52 |
    53 | this.setState(updateByPropertyName('email', event.target.value))} 56 | type="text" 57 | placeholder="Email Address" 58 | /> 59 | 62 | 63 | { error &&

    {error.message}

    } 64 |
    65 | ); 66 | } 67 | } 68 | 69 | const PasswordForgetLink = () => 70 |

    71 | Forgot Password? 72 |

    73 | 74 | export default PasswordForgetPage; 75 | 76 | export { 77 | PasswordForgetForm, 78 | PasswordForgetLink, 79 | }; 80 | -------------------------------------------------------------------------------- /src/components/Session/withAuthentication.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { firebase } from '../../firebase'; 5 | 6 | const withAuthentication = (Component) => { 7 | class WithAuthentication extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | authUser: null, 13 | }; 14 | } 15 | 16 | getChildContext() { 17 | return { 18 | authUser: this.state.authUser, 19 | }; 20 | } 21 | 22 | componentDidMount() { 23 | firebase.auth.onAuthStateChanged(authUser => { 24 | authUser 25 | ? this.setState(() => ({ authUser })) 26 | : this.setState(() => ({ authUser: null })); 27 | }); 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | ); 34 | } 35 | } 36 | 37 | WithAuthentication.childContextTypes = { 38 | authUser: PropTypes.object, 39 | }; 40 | 41 | return WithAuthentication; 42 | } 43 | 44 | export default withAuthentication; -------------------------------------------------------------------------------- /src/components/Session/withAuthorization.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withRouter } from 'react-router-dom'; 4 | 5 | import { firebase } from '../../firebase'; 6 | import * as routes from '../../constants/routes'; 7 | 8 | const withAuthorization = (condition) => (Component) => { 9 | class WithAuthorization extends React.Component { 10 | componentDidMount() { 11 | firebase.auth.onAuthStateChanged(authUser => { 12 | if (!condition(authUser)) { 13 | this.props.history.push(routes.SIGN_IN); 14 | } 15 | }); 16 | } 17 | 18 | render() { 19 | return this.context.authUser ? : null; 20 | } 21 | } 22 | 23 | WithAuthorization.contextTypes = { 24 | authUser: PropTypes.object, 25 | }; 26 | 27 | return withRouter(WithAuthorization); 28 | } 29 | 30 | export default withAuthorization; -------------------------------------------------------------------------------- /src/components/SignIn/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import { SignUpLink } from '../SignUp'; 5 | import { PasswordForgetLink } from '../PasswordForget'; 6 | import { auth } from '../../firebase'; 7 | import * as routes from '../../constants/routes'; 8 | 9 | const SignInPage = ({ history }) => 10 |
    11 |

    SignIn

    12 | 13 | 14 | 15 |
    16 | 17 | const updateByPropertyName = (propertyName, value) => () => ({ 18 | [propertyName]: value, 19 | }); 20 | 21 | const INITIAL_STATE = { 22 | email: '', 23 | password: '', 24 | error: null, 25 | }; 26 | 27 | class SignInForm extends Component { 28 | constructor(props) { 29 | super(props); 30 | 31 | this.state = { ...INITIAL_STATE }; 32 | } 33 | 34 | onSubmit = (event) => { 35 | const { 36 | email, 37 | password, 38 | } = this.state; 39 | 40 | const { 41 | history, 42 | } = this.props; 43 | 44 | auth.doSignInWithEmailAndPassword(email, password) 45 | .then(() => { 46 | this.setState(() => ({ ...INITIAL_STATE })); 47 | history.push(routes.HOME); 48 | }) 49 | .catch(error => { 50 | this.setState(updateByPropertyName('error', error)); 51 | }); 52 | 53 | event.preventDefault(); 54 | } 55 | 56 | render() { 57 | const { 58 | email, 59 | password, 60 | error, 61 | } = this.state; 62 | 63 | const isInvalid = 64 | password === '' || 65 | email === ''; 66 | 67 | return ( 68 |
    69 | this.setState(updateByPropertyName('email', event.target.value))} 72 | type="text" 73 | placeholder="Email Address" 74 | /> 75 | this.setState(updateByPropertyName('password', event.target.value))} 78 | type="password" 79 | placeholder="Password" 80 | /> 81 | 84 | 85 | { error &&

    {error.message}

    } 86 |
    87 | ); 88 | } 89 | } 90 | 91 | export default withRouter(SignInPage); 92 | 93 | export { 94 | SignInForm, 95 | }; 96 | -------------------------------------------------------------------------------- /src/components/SignOut/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { auth } from '../../firebase'; 4 | 5 | const SignOutButton = () => 6 | 12 | 13 | export default SignOutButton; 14 | -------------------------------------------------------------------------------- /src/components/SignUp/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Link, 4 | withRouter, 5 | } from 'react-router-dom'; 6 | 7 | import { auth, db } from '../../firebase'; 8 | import * as routes from '../../constants/routes'; 9 | 10 | const SignUpPage = ({ history }) => 11 |
    12 |

    SignUp

    13 | 14 |
    15 | 16 | const updateByPropertyName = (propertyName, value) => () => ({ 17 | [propertyName]: value, 18 | }); 19 | 20 | const INITIAL_STATE = { 21 | username: '', 22 | email: '', 23 | passwordOne: '', 24 | passwordTwo: '', 25 | error: null, 26 | }; 27 | 28 | class SignUpForm extends Component { 29 | constructor(props) { 30 | super(props); 31 | 32 | this.state = { ...INITIAL_STATE }; 33 | } 34 | 35 | onSubmit = (event) => { 36 | const { 37 | username, 38 | email, 39 | passwordOne, 40 | } = this.state; 41 | 42 | const { 43 | history, 44 | } = this.props; 45 | 46 | auth.doCreateUserWithEmailAndPassword(email, passwordOne) 47 | .then(authUser => { 48 | 49 | // Create a user in your own accessible Firebase Database too 50 | db.doCreateUser(authUser.uid, username, email) 51 | .then(() => { 52 | this.setState(() => ({ ...INITIAL_STATE })); 53 | history.push(routes.HOME); 54 | }) 55 | .catch(error => { 56 | this.setState(updateByPropertyName('error', error)); 57 | }); 58 | 59 | }) 60 | .catch(error => { 61 | this.setState(updateByPropertyName('error', error)); 62 | }); 63 | 64 | event.preventDefault(); 65 | } 66 | 67 | render() { 68 | const { 69 | username, 70 | email, 71 | passwordOne, 72 | passwordTwo, 73 | error, 74 | } = this.state; 75 | 76 | const isInvalid = 77 | passwordOne !== passwordTwo || 78 | passwordOne === '' || 79 | username === '' || 80 | email === ''; 81 | 82 | return ( 83 |
    84 | this.setState(updateByPropertyName('username', event.target.value))} 87 | type="text" 88 | placeholder="Full Name" 89 | /> 90 | this.setState(updateByPropertyName('email', event.target.value))} 93 | type="text" 94 | placeholder="Email Address" 95 | /> 96 | this.setState(updateByPropertyName('passwordOne', event.target.value))} 99 | type="password" 100 | placeholder="Password" 101 | /> 102 | this.setState(updateByPropertyName('passwordTwo', event.target.value))} 105 | type="password" 106 | placeholder="Confirm Password" 107 | /> 108 | 111 | 112 | { error &&

    {error.message}

    } 113 |
    114 | ); 115 | } 116 | } 117 | 118 | const SignUpLink = () => 119 |

    120 | Don't have an account? 121 | {' '} 122 | Sign Up 123 |

    124 | 125 | export default withRouter(SignUpPage); 126 | 127 | export { 128 | SignUpForm, 129 | SignUpLink, 130 | }; -------------------------------------------------------------------------------- /src/constants/routes.js: -------------------------------------------------------------------------------- 1 | export const LANDING = '/'; 2 | export const SIGN_UP = '/signup'; 3 | export const SIGN_IN = '/signin'; 4 | export const PASSWORD_FORGET = '/pw-forget'; 5 | export const HOME = '/home'; 6 | export const ACCOUNT = '/account'; 7 | -------------------------------------------------------------------------------- /src/firebase/auth.js: -------------------------------------------------------------------------------- 1 | import { auth } from './firebase'; 2 | 3 | // Sign Up 4 | export const doCreateUserWithEmailAndPassword = (email, password) => 5 | auth.createUserWithEmailAndPassword(email, password); 6 | 7 | // Sign In 8 | export const doSignInWithEmailAndPassword = (email, password) => 9 | auth.signInWithEmailAndPassword(email, password); 10 | 11 | // Sign out 12 | export const doSignOut = () => 13 | auth.signOut(); 14 | 15 | // Password Reset 16 | export const doPasswordReset = (email) => 17 | auth.sendPasswordResetEmail(email); 18 | 19 | // Password Change 20 | export const doPasswordUpdate = (password) => 21 | auth.currentUser.updatePassword(password); 22 | -------------------------------------------------------------------------------- /src/firebase/db.js: -------------------------------------------------------------------------------- 1 | import { db } from './firebase'; 2 | 3 | // User API 4 | 5 | export const doCreateUser = (id, username, email) => 6 | db.ref(`users/${id}`).set({ 7 | username, 8 | email, 9 | }); 10 | 11 | export const onceGetUsers = () => 12 | db.ref('users').once('value'); 13 | 14 | // Chat API 15 | 16 | export const doCreateMessage = (userId, text) => 17 | db.ref('messages').push({ 18 | userId, 19 | text, 20 | }); 21 | 22 | export const onMessageAdded = (callback) => 23 | db.ref('messages') 24 | .orderByKey() 25 | .limitToLast(100) 26 | .on('child_added', callback); 27 | 28 | -------------------------------------------------------------------------------- /src/firebase/firebase.js: -------------------------------------------------------------------------------- 1 | import * as firebase from 'firebase'; 2 | 3 | const prodConfig = { 4 | apiKey: YOUR_API_KEY, 5 | authDomain: YOUR_AUTH_DOMAIN, 6 | databaseURL: YOUR_DATABASE_URL, 7 | projectId: YOUR_PROJECT_ID, 8 | storageBucket: '', 9 | messagingSenderId: YOUR_MESSAGING_SENDER_ID, 10 | }; 11 | 12 | const devConfig = { 13 | apiKey: YOUR_API_KEY, 14 | authDomain: YOUR_AUTH_DOMAIN, 15 | databaseURL: YOUR_DATABASE_URL, 16 | projectId: YOUR_PROJECT_ID, 17 | storageBucket: '', 18 | messagingSenderId: YOUR_MESSAGING_SENDER_ID, 19 | }; 20 | 21 | const config = process.env.NODE_ENV === 'production' 22 | ? prodConfig 23 | : devConfig; 24 | 25 | if (!firebase.apps.length) { 26 | firebase.initializeApp(config); 27 | } 28 | 29 | const db = firebase.database(); 30 | const auth = firebase.auth(); 31 | 32 | export { 33 | db, 34 | auth, 35 | }; 36 | -------------------------------------------------------------------------------- /src/firebase/index.js: -------------------------------------------------------------------------------- 1 | import * as auth from './auth'; 2 | import * as db from './db'; 3 | import * as firebase from './firebase'; 4 | 5 | export { 6 | auth, 7 | db, 8 | firebase, 9 | }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './components/App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | --------------------------------------------------------------------------------