├── .babelrc ├── .codeclimate.yml ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── examples ├── example │ ├── .babelrc │ ├── .eslintrc │ ├── README.md │ ├── jsconfig.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── api │ │ │ └── sessionApi.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── Home.js │ │ │ ├── Input.js │ │ │ ├── Login.js │ │ │ ├── Logout.js │ │ │ └── SessionInfo.js │ │ ├── index.html │ │ ├── index.js │ │ └── stores │ │ │ └── UserStore.js │ └── webpack.config.js └── react-router-v4-example │ ├── .babelrc │ ├── .eslintrc │ ├── README.md │ ├── jsconfig.json │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── api │ │ └── sessionApi.js │ ├── components │ │ ├── App.js │ │ ├── Home.js │ │ ├── Input.js │ │ ├── Login.js │ │ ├── Logout.js │ │ ├── PrivateRoute.js │ │ └── SessionInfo.js │ ├── index.html │ ├── index.js │ └── stores │ │ └── UserStore.js │ └── webpack.config.js ├── package-lock.json ├── package.json └── src ├── __tests__ └── index.test.js ├── constants.js ├── index.js └── storage.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-proposal-class-properties", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - "examples/" 3 | - "src/__tests__/" 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:import/warnings" 6 | ], 7 | "plugins": [], 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 6, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "env": { 18 | "es6": true, 19 | "browser": true, 20 | "node": true, 21 | "jquery": true, 22 | "jest": true 23 | }, 24 | "rules": { 25 | "quotes": 0, 26 | "no-console": 1, 27 | "no-debugger": 1, 28 | "no-var": 1, 29 | "semi": [1, "always"], 30 | "no-trailing-spaces": 1, 31 | "eol-last": 1, 32 | "no-underscore-dangle": 0, 33 | "no-alert": 0, 34 | "no-lone-blocks": 0, 35 | "jsx-quotes": 1, 36 | "no-multi-spaces": 1, 37 | "block-spacing": 1, 38 | "brace-style": 1, 39 | "comma-spacing": [1, { "before": false, "after": true }], 40 | "comma-style": 1, 41 | "key-spacing": 1, 42 | "no-multiple-empty-lines": [1, { "max": 1 }], 43 | "arrow-spacing": 1, 44 | "no-const-assign": 1, 45 | "object-curly-spacing": [1, "always"], 46 | "space-before-blocks" : [1, "always"], 47 | "keyword-spacing": 1, 48 | "indent": [1, 2, { "SwitchCase": 1 }] 49 | }, 50 | "globals": {} 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist 4 | 5 | coverage 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | src 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: npm 5 | 6 | env: 7 | global: 8 | - CC_TEST_REPORTER_ID=5f93914c53e72710d78b95109895e11eaea0ca6aad5b1b9d2ab6cdd183902b00 9 | 10 | before_script: 11 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 12 | - chmod +x ./cc-test-reporter 13 | - ./cc-test-reporter before-build 14 | 15 | script: 16 | - "npm run test:cover" 17 | - "npm run lint" 18 | 19 | after_script: 20 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | 3 | Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | 25 | Examples of unacceptable behavior by participants include: 26 | 27 | 28 | * The use of sexualized language or imagery and unwelcome sexual attention or 29 | advances 30 | * Trolling, insulting/derogatory comments, and personal or political attacks 31 | * Public or private harassment 32 | * Publishing others’ private information, such as a physical or electronic 33 | address, without explicit permission 34 | * Other conduct which could reasonably be considered inappropriate in a 35 | professional setting 36 | 37 | 38 | Our Responsibilities 39 | 40 | Project maintainers are responsible for clarifying the standards of acceptable 41 | behavior and are expected to take appropriate and fair corrective action in 42 | response to any instances of unacceptable behavior. 43 | 44 | Project maintainers have the right and responsibility to remove, edit, or 45 | reject comments, commits, code, wiki edits, issues, and other contributions 46 | that are not aligned to this Code of Conduct, or to ban temporarily or 47 | permanently any contributor for other behaviors that they deem inappropriate, 48 | threatening, offensive, or harmful. 49 | 50 | Scope 51 | 52 | This Code of Conduct applies both within project spaces and in public spaces 53 | when an individual is representing the project or its community. Examples of 54 | representing a project or community include using an official project e-mail 55 | address, posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. Representation of a project may be 57 | further defined and clarified by project maintainers. 58 | 59 | Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting the project team at ricardo@rootstrap.com. All 63 | complaints will be reviewed and investigated and will result in a response that 64 | is deemed necessary and appropriate to the circumstances. The project team is 65 | obligated to maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted separately. 67 | 68 | Project maintainers who do not follow or enforce the Code of Conduct in good 69 | faith may face temporary or permanent repercussions as determined by other 70 | members of the project’s leadership. 71 | 72 | Attribution 73 | 74 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 75 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing ## 2 | 3 | You can contribute to this repo if you have an issue, found a bug or think there's some functionality required that would add value to the gem. To do so, please check if there's not already an [issue](https://github.com/rootstrap/mobx-session/issues) for that, if you find there's not, create a new one with as much detail as possible. 4 | 5 | If you want to contribute with code as well, please follow the next steps: 6 | 7 | 1. Read, understand and agree to our [code of conduct](https://github.com/rootstrap/mobx-session/blob/master/CODE_OF_CONDUCT.md) 8 | 2. [Fork the repo](https://help.github.com/articles/about-forks/) 9 | 3. Clone the project into your machine: 10 | `$ git clone git@github.com:[YOUR GITHUB USERNAME]/mobx-session.git` 11 | 4. Access the repo: 12 | `$ cd mobx-session` 13 | 5. Create your feature/bugfix branch: 14 | `$ git checkout -b your_new_feature` 15 | or 16 | `$ git checkout -b fix/your_fix` in case of a bug fix 17 | (if your PR is to address an existing issue, it would be good to name the branch after the issue, for example: if you are trying to solve issue 182, then a good idea for the branch name would be `182_your_new_feature`) 18 | 6. Write tests for your changes (feature/bug) 19 | 7. Code your (feature/bugfix) 20 | 8. Run the code analysis tool by doing: 21 | `$ rake code_analysis` 22 | 9. Run the tests: 23 | `$ bundle exec rspec` 24 | All tests must pass. If all tests (both code analysis and rspec) do pass, then you are ready to go to the next step: 25 | 10. Commit your changes: 26 | `$ git commit -m 'Your feature or bugfix title'` 27 | 11. Push to the branch `$ git push origin your_new_feature` 28 | 12. Create a new [pull request](https://help.github.com/articles/creating-a-pull-request/) 29 | 30 | Some helpful guides that will help you know how we work: 31 | 1. [Code review](https://github.com/rootstrap/tech-guides/tree/master/code-review) 32 | 2. [GIT workflow](https://github.com/rootstrap/tech-guides/tree/master/git) 33 | 3. [Javascript](https://github.com/airbnb/javascript) 34 | 35 | For more information or guides like the ones mentioned above, please check our [tech guides](https://github.com/rootstrap/tech-guides). Keep in mind that the more you know about these guides, the easier it will be for your code to get approved and merged. 36 | 37 | Note: We work with one commit per pull request, so if you make your commit and realize you were missing something or want to add something more to it, don't create a new commit with the changes, but use `$ git commit --amend` instead. This same principle also applies for when changes are requested on an open pull request. 38 | 39 | 40 | Thank you very much for your time and for considering helping in this project. 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/rootstrap/mobx-session.svg?branch=master)](https://travis-ci.com/rootstrap/mobx-session) 2 | [![Maintainability](https://api.codeclimate.com/v1/badges/7779ab5005e559a3104a/maintainability)](https://codeclimate.com/github/rootstrap/mobx-session/maintainability) 3 | [![Test Coverage](https://api.codeclimate.com/v1/badges/7779ab5005e559a3104a/test_coverage)](https://codeclimate.com/github/rootstrap/mobx-session/test_coverage) 4 | 5 | # Mobx Session 6 | 7 | Mobx Session helps you manage your session data providing an API to save and access your info whenever and wherever you want. 8 | 9 | The stored data will be saved with [localforage](https://github.com/localForage/localForage). 10 | 11 | ## Installation 12 | yarn: 13 | 14 | `yarn add mobx-session` 15 | 16 | npm: 17 | 18 | `npm install mobx-session` 19 | 20 | ## Usage 21 | 22 | It's really straight forward to use. 23 | 24 | First you must initialize the Storage. For that, you should call 25 | 26 | ```javascript 27 | import SessionStore from 'mobx-session'; 28 | 29 | SessionStore.initialize(); 30 | ``` 31 | 32 | **This should be called before any other method call, so I would recommend putting it on the index.js or App of your site, or in the top of another Store that uses SessionStore (like in [this example](examples/example/src/stores/UserStore.js)).** 33 | 34 | There are several config options to customize this method, go to the [API doc](https://github.com/rootstrap/mobx-session#initializeconfig-object-promise) to see more. 35 | 36 | For example 37 | 38 | ```javascript 39 | import SessionStore from 'mobx-session'; 40 | 41 | SessionStore.initialize({ name: 'my-app-name' }); 42 | ``` 43 | 44 | Then, you can use it wherever you want. 45 | 46 | ### Inside a Component 47 | 48 | ```javascript 49 | import SessionStore from 'mobx-session'; 50 | 51 | const MyComponent = observer(() => { 52 | if (SessionStore.initialized) { 53 | return ( 54 |
55 | { 56 | SessionStore.hasSession 57 | ?

Hello!

58 | :

Login

59 | } 60 |
61 | ); 62 | } 63 | 64 | return null; 65 | }) 66 | ``` 67 | 68 | You can use the decorator syntax 69 | 70 | ```javascript 71 | import SessionStore from 'mobx-session'; 72 | 73 | @observer 74 | const MyComponent = () => { 75 | if (SessionStore.initialized) { 76 | return ( 77 |
78 | { 79 | SessionStore.hasSession 80 | ?

Hello!

81 | :

Login

82 | } 83 |
84 | ); 85 | } 86 | 87 | return null; 88 | } 89 | ``` 90 | 91 | *TIP: don't forget to make your component an observer so you don't lose the reference.* 92 | 93 | ### Inside a Store 94 | 95 | ```javascript 96 | import SessionStore from 'mobx-session'; 97 | 98 | class UserStore { 99 | constructor() { 100 | extendObservable(this, { 101 | user: null, 102 | get loggedIn() { 103 | return this.user !== null && SessionStore.hasSession; 104 | }, 105 | }); 106 | } 107 | ...... 108 | } 109 | ``` 110 | 111 | You can also use the decorator syntax 112 | 113 | ```javascript 114 | import SessionStore from 'mobx-session'; 115 | 116 | class UserStore { 117 | @observable user = null; 118 | @computed get loggedIn() { 119 | return this.user !== null && SessionStore.hasSession; 120 | } 121 | ...... 122 | } 123 | ``` 124 | 125 | *TIP: inside a store, don't forget to use it inside a computed value or an auto-run so you don't loose the reference.* 126 | 127 | ## Examples 128 | 129 | - [Basic example using React](examples/example) 130 | - [Example using React and React Router v4](examples/react-router-v4-example) that uses the session data to determine if a user can access a private route. 131 | 132 | ## API 133 | 134 | ### initialize(config: object): Promise 135 | 136 | Initialize an instance of the storage inside the session. 137 | 138 | Options can be the ones listed in the [localforage library](https://github.com/localForage/localForage#configuration) 139 | 140 | ### saveSession(session: object): Promise 141 | 142 | Saves the session object in the store and storage 143 | 144 | ### deleteSession(): Promise 145 | 146 | Deletes the session object from the store and the storage 147 | 148 | ### getSession(): Promise(session: object) 149 | 150 | Returns the session object saved if there's any 151 | 152 | ### initialized: boolean 153 | 154 | Returns true if the session store has been initialized. Could be useful to check this property before relying on other methods or properties from the store. 155 | 156 | ### hasSession: boolean 157 | 158 | Returns true if there's a session object saved and false if there's not. 159 | 160 | ## Contributing 161 | Bug reports (please use Issues) and pull requests are welcome on GitHub at https://github.com/rootstrap/mobx-session. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 162 | 163 | ## License 164 | The library is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 165 | 166 | ## Credits 167 | **mobx-session** is maintained by [Rootstrap](http://www.rootstrap.com) with the help of our [contributors](https://github.com/rootstrap/mobx-session/contributors). 168 | 169 | [](http://www.rootstrap.com) 170 | -------------------------------------------------------------------------------- /examples/example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/react" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-proposal-decorators", 9 | { 10 | "legacy": true 11 | } 12 | ], 13 | [ 14 | "@babel/plugin-proposal-class-properties", 15 | { 16 | "loose": true 17 | } 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:import/warnings", 6 | "airbnb" 7 | ], 8 | "plugins": [ 9 | "react", 10 | "react-hooks" 11 | ], 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "ecmaVersion": 6, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true, 18 | "experimentalObjectRestSpread": true 19 | } 20 | }, 21 | "env": { 22 | "es6": true, 23 | "browser": true, 24 | "node": true, 25 | "jquery": true, 26 | "jest": true 27 | }, 28 | "rules": { 29 | "object-curly-newline": 0, 30 | "no-class-assign": 0, 31 | "no-param-reassign": 0, 32 | "class-methods-use-this": 0, 33 | "consistent-return": 0, 34 | "no-shadow": 0, 35 | "global-require": 0, 36 | "eqeqeq": 0, 37 | "no-unused-expressions": [1, { "allowShortCircuit": true, "allowTernary": true }], 38 | "array-callback-return": 0, 39 | "no-console": [1, { "allow": ["warn", "error"] }], 40 | "no-debugger": 1, 41 | "no-var": 1, 42 | "semi": [1, "always"], 43 | "no-trailing-spaces": 1, 44 | "eol-last": 1, 45 | "no-underscore-dangle": 0, 46 | "no-alert": 0, 47 | "no-lone-blocks": 0, 48 | "jsx-quotes": 1, 49 | "no-multi-spaces": 1, 50 | "block-spacing": 1, 51 | "brace-style": 1, 52 | "comma-dangle": 0, 53 | "comma-spacing": [1, { "before": false, "after": true }], 54 | "comma-style": 1, 55 | "key-spacing": 1, 56 | "no-empty": [1, { "allowEmptyCatch": true }], 57 | "no-multiple-empty-lines": [1, { "max": 1 }], 58 | "arrow-spacing": 1, 59 | "no-const-assign": 1, 60 | "object-curly-spacing": [1, "always"], 61 | "space-before-blocks" : [1, "always"], 62 | "keyword-spacing": 1, 63 | "indent": [1, 2, { "SwitchCase": 1 }], 64 | "max-len": 0, 65 | "jsx-a11y/click-events-have-key-events": 0, 66 | "jsx-a11y/label-has-for": 0, 67 | "jsx-a11y/no-static-element-interactions": 0, 68 | "jsx-a11y/anchor-is-valid": 0, 69 | "react/display-name": [ 1, { "ignoreTranspilerName": false }], 70 | "react/forbid-prop-types": [1, { "forbid": ["any", "array", "object"] }], 71 | "react/jsx-curly-spacing": 1, 72 | "react/jsx-filename-extension": 0, 73 | "react/jsx-indent-props": 0, 74 | "react/jsx-key": 1, 75 | "react/jsx-max-props-per-line": 0, 76 | "react/jsx-no-duplicate-props": 1, 77 | "react/jsx-no-literals": 0, 78 | "react/jsx-no-undef": 1, 79 | "react/jsx-pascal-case": 1, 80 | "react/jsx-sort-prop-types": 0, 81 | "react/jsx-sort-props": 0, 82 | "react/jsx-uses-react": 1, 83 | "react/jsx-uses-vars": 1, 84 | "react/no-danger": 1, 85 | "react/no-did-mount-set-state": 1, 86 | "react/no-did-update-set-state": 1, 87 | "react/no-direct-mutation-state": 1, 88 | "react/no-multi-comp": 1, 89 | "react/no-set-state": 0, 90 | "react/no-unknown-property": 1, 91 | "react/prefer-es6-class": 1, 92 | "react/prop-types": 1, 93 | "react/react-in-jsx-scope": 1, 94 | "react/require-default-props": 0, 95 | "react/self-closing-comp": 1, 96 | "react/sort-comp": 1, 97 | "react/jsx-wrap-multilines": 0, 98 | "react/no-array-index-key": 0, 99 | "import/extensions": 1, 100 | "import/prefer-default-export": 0, 101 | "import/no-extraneous-dependencies": 0, 102 | "import/no-named-as-default": 0, 103 | "react-hooks/rules-of-hooks": "error" 104 | }, 105 | "settings": { 106 | "import/resolver": { 107 | "node": { 108 | "paths": ["src"] 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/example/README.md: -------------------------------------------------------------------------------- 1 | # Simple Example 2 | 3 | Example that shows basic usage of [mobx-session](../..) 4 | 5 | ## Installation 6 | ### Install dependencies 7 | `npm install` 8 | 9 | ## Running the example 10 | `npm run start` 11 | -------------------------------------------------------------------------------- /examples/example/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "*": ["src/*"], 6 | "api/*": ["src/api/*"], 7 | "components/*": ["src/components/*"], 8 | "constants/*": ["src/constants/*"], 9 | "pages/*": ["src/pages/*"], 10 | "utils/*": ["src/utils/*"], 11 | "stores/*": ["src/stores/*"], 12 | } 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /examples/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-for-mobx-session", 3 | "version": "1.0.0", 4 | "description": "Basic example for the mobx-session library", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "babel-node ./node_modules/webpack-dev-server/bin/webpack-dev-server --open", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint src" 10 | }, 11 | "keywords": [], 12 | "author": "Rootstrap", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@babel/core": "^7.4.5", 16 | "@babel/node": "^7.4.5", 17 | "@babel/plugin-proposal-class-properties": "^7.4.4", 18 | "@babel/plugin-proposal-decorators": "^7.4.4", 19 | "@babel/preset-env": "^7.4.5", 20 | "@babel/preset-react": "^7.0.0", 21 | "babel-loader": "^8.0.6", 22 | "eslint": "4.19.1", 23 | "eslint-config-airbnb": "16.1.0", 24 | "eslint-plugin-import": "2.11.0", 25 | "eslint-plugin-jsx-a11y": "6.0.3", 26 | "eslint-plugin-react": "7.7.0", 27 | "eslint-plugin-react-hooks": "1.0.0", 28 | "file-loader": "^4.0.0", 29 | "html-webpack-plugin": "^3.2.0", 30 | "path": "^0.12.7", 31 | "webpack": "^4.33.0", 32 | "webpack-cli": "^3.3.4", 33 | "webpack-dev-server": "^3.7.1" 34 | }, 35 | "dependencies": { 36 | "mobx": "^5.10.1", 37 | "mobx-react": "^6.0.3", 38 | "mobx-session": "file:../../", 39 | "prop-types": "^15.7.2", 40 | "react": "^16.8.6", 41 | "react-dom": "^16.8.6" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/example/src/api/sessionApi.js: -------------------------------------------------------------------------------- 1 | const login = (user) => { 2 | const response = { 3 | token: '1a2b3c4d', 4 | data: { 5 | email: user.email, 6 | firstName: 'test', 7 | lastName: 'test' 8 | } 9 | }; 10 | return new Promise(resolve => setTimeout(resolve(response), 300)); 11 | }; 12 | 13 | const logout = () => new Promise(resolve => setTimeout(resolve, 300)); 14 | 15 | export default { login, logout }; 16 | -------------------------------------------------------------------------------- /examples/example/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Home from './Home'; 4 | 5 | const App = () => ( 6 |
7 | 8 |
9 | ); 10 | 11 | export default App; 12 | -------------------------------------------------------------------------------- /examples/example/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import SessionStore from 'mobx-session'; 4 | 5 | import Login from 'components/Login'; 6 | import Logout from 'components/Logout'; 7 | import SessionInfo from 'components/SessionInfo'; 8 | 9 | const Home = observer(() => { 10 | if (SessionStore.initialized) { 11 | return ( 12 |
13 | 14 | { 15 | SessionStore.hasSession 16 | ? 17 | : 18 | } 19 |
20 | ); 21 | } 22 | 23 | return null; 24 | }); 25 | 26 | export default Home; 27 | -------------------------------------------------------------------------------- /examples/example/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { func, string } from 'prop-types'; 3 | 4 | const inputStyle = { 5 | padding: 10 6 | }; 7 | 8 | const Input = ({ onChange, type, name }) => ( 9 |
10 | 11 |
12 | onChange(value)} 17 | /> 18 |
19 | ); 20 | 21 | Input.propTypes = { 22 | onChange: func.isRequired, 23 | type: string.isRequired, 24 | name: string.isRequired 25 | }; 26 | 27 | export default Input; 28 | -------------------------------------------------------------------------------- /examples/example/src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import UserStore from 'stores/UserStore'; 4 | import Input from 'components/Input'; 5 | 6 | const Login = () => { 7 | const [email, updateEmail] = useState(''); 8 | const [password, updatePassword] = useState(''); 9 | 10 | const onSubmit = () => { 11 | if (email && password) { 12 | UserStore.login({ email, password }); 13 | } 14 | }; 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 |
23 | ); 24 | }; 25 | 26 | export default Login; 27 | -------------------------------------------------------------------------------- /examples/example/src/components/Logout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import UserStore from 'stores/UserStore'; 4 | 5 | const Logout = () => ( 6 |
7 | Try refreshing the page to see that the session is saved 8 |
9 | 10 |
11 | ); 12 | 13 | export default Logout; 14 | -------------------------------------------------------------------------------- /examples/example/src/components/SessionInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import UserStore from 'stores/UserStore'; 5 | 6 | const infoStyles = { 7 | backgroundColor: 'lightgray', 8 | padding: 20, 9 | margin: 10 10 | }; 11 | 12 | const SessionInfo = observer(({ userInfo: { user } }) => ( 13 |
14 |

Current Session Info:

15 | { user ? 16 |
17 | email: { user.email }
18 | firstName: { user.firstName }
19 | lastName: { user.lastName }
20 |
21 | :

No session info

22 | } 23 |
24 |
25 | )); 26 | 27 | const ConnectedSessionInfo = () => ; 28 | 29 | export default ConnectedSessionInfo; 30 | -------------------------------------------------------------------------------- /examples/example/src/index.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from 'components/App'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ); 9 | -------------------------------------------------------------------------------- /examples/example/src/stores/UserStore.js: -------------------------------------------------------------------------------- 1 | import { extendObservable, runInAction } from 'mobx'; 2 | import SessionStore from 'mobx-session'; 3 | 4 | import sessionApi from 'api/sessionApi'; 5 | 6 | class Store { 7 | constructor() { 8 | SessionStore.initialize({ name: 'example-app' }); 9 | 10 | extendObservable(this, { 11 | user: null, 12 | loginError: false, 13 | logoutError: false, 14 | get loggedIn() { 15 | return this.user !== null && SessionStore.hasSession; 16 | } 17 | }); 18 | 19 | runInAction('Load user', async () => { 20 | this.user = await SessionStore.getSession(); 21 | }); 22 | } 23 | 24 | saveUser = (session) => { 25 | SessionStore.saveSession(session); 26 | runInAction('Save user', () => { 27 | this.user = session; 28 | }); 29 | } 30 | 31 | removeUser = () => { 32 | SessionStore.deleteSession(); 33 | runInAction('Logout user', () => { 34 | this.user = null; 35 | }); 36 | } 37 | 38 | login = async (user) => { 39 | try { 40 | runInAction('Init Login', () => { 41 | this.loginError = false; 42 | }); 43 | const { data } = await sessionApi.login(user); 44 | this.saveUser(data); 45 | } catch (error) { 46 | runInAction('Error Login', () => { 47 | this.loginError = error.errors; 48 | }); 49 | } 50 | } 51 | 52 | logout = async () => { 53 | try { 54 | runInAction('Init Logout', () => { 55 | this.logoutError = false; 56 | }); 57 | await sessionApi.logout(); 58 | this.removeUser(); 59 | } catch (error) { 60 | runInAction('Error Logout', () => { 61 | this.logoutError = error.errors; 62 | }); 63 | } 64 | } 65 | } 66 | 67 | const UserStore = new Store(); 68 | 69 | export default UserStore; 70 | -------------------------------------------------------------------------------- /examples/example/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import "@babel/polyfill"; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | 5 | module.exports = { 6 | entry: [ 7 | "@babel/polyfill", 8 | path.resolve(__dirname, 'src/index.js') 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'build'), 12 | filename: 'index.bundle.js' 13 | }, 14 | mode: process.env.NODE_ENV || 'development', 15 | resolve: { 16 | modules: [path.resolve(__dirname, 'src'), 'node_modules'] 17 | }, 18 | devServer: { 19 | contentBase: path.join(__dirname, 'src') 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | // this is so that we can compile any React, 25 | // ES6 and above into normal ES5 syntax 26 | test: /\.(js|jsx)$/, 27 | // we do not want anything from node_modules to be compiled 28 | exclude: /node_modules/, 29 | use: ['babel-loader'] 30 | }, 31 | { 32 | test: /\.(jpg|jpeg|png|gif|mp3|svg)$/, 33 | loaders: ['file-loader'] 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new HtmlWebpackPlugin({ 39 | template: path.join(__dirname, 'src', 'index.html') 40 | }) 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env", 4 | "@babel/react" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-proposal-decorators", 9 | { 10 | "legacy": true 11 | } 12 | ], 13 | [ 14 | "@babel/plugin-proposal-class-properties", 15 | { 16 | "loose": true 17 | } 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:import/warnings", 6 | "airbnb" 7 | ], 8 | "plugins": [ 9 | "react", 10 | "react-hooks" 11 | ], 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "ecmaVersion": 6, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true, 18 | "experimentalObjectRestSpread": true 19 | } 20 | }, 21 | "env": { 22 | "es6": true, 23 | "browser": true, 24 | "node": true, 25 | "jquery": true, 26 | "jest": true 27 | }, 28 | "rules": { 29 | "object-curly-newline": 0, 30 | "no-class-assign": 0, 31 | "no-param-reassign": 0, 32 | "class-methods-use-this": 0, 33 | "consistent-return": 0, 34 | "no-shadow": 0, 35 | "global-require": 0, 36 | "eqeqeq": 0, 37 | "no-unused-expressions": [1, { "allowShortCircuit": true, "allowTernary": true }], 38 | "array-callback-return": 0, 39 | "no-console": [1, { "allow": ["warn", "error"] }], 40 | "no-debugger": 1, 41 | "no-var": 1, 42 | "semi": [1, "always"], 43 | "no-trailing-spaces": 1, 44 | "eol-last": 1, 45 | "no-underscore-dangle": 0, 46 | "no-alert": 0, 47 | "no-lone-blocks": 0, 48 | "jsx-quotes": 1, 49 | "no-multi-spaces": 1, 50 | "block-spacing": 1, 51 | "brace-style": 1, 52 | "comma-dangle": 0, 53 | "comma-spacing": [1, { "before": false, "after": true }], 54 | "comma-style": 1, 55 | "key-spacing": 1, 56 | "no-empty": [1, { "allowEmptyCatch": true }], 57 | "no-multiple-empty-lines": [1, { "max": 1 }], 58 | "arrow-spacing": 1, 59 | "no-const-assign": 1, 60 | "object-curly-spacing": [1, "always"], 61 | "space-before-blocks" : [1, "always"], 62 | "keyword-spacing": 1, 63 | "indent": [1, 2, { "SwitchCase": 1 }], 64 | "max-len": 0, 65 | "jsx-a11y/click-events-have-key-events": 0, 66 | "jsx-a11y/label-has-for": 0, 67 | "jsx-a11y/no-static-element-interactions": 0, 68 | "jsx-a11y/anchor-is-valid": 0, 69 | "react/display-name": [ 1, { "ignoreTranspilerName": false }], 70 | "react/forbid-prop-types": [1, { "forbid": ["any", "array", "object"] }], 71 | "react/jsx-curly-spacing": 1, 72 | "react/jsx-filename-extension": 0, 73 | "react/jsx-indent-props": 0, 74 | "react/jsx-key": 1, 75 | "react/jsx-max-props-per-line": 0, 76 | "react/jsx-no-duplicate-props": 1, 77 | "react/jsx-no-literals": 0, 78 | "react/jsx-no-undef": 1, 79 | "react/jsx-pascal-case": 1, 80 | "react/jsx-sort-prop-types": 0, 81 | "react/jsx-sort-props": 0, 82 | "react/jsx-uses-react": 1, 83 | "react/jsx-uses-vars": 1, 84 | "react/no-danger": 1, 85 | "react/no-did-mount-set-state": 1, 86 | "react/no-did-update-set-state": 1, 87 | "react/no-direct-mutation-state": 1, 88 | "react/no-multi-comp": 1, 89 | "react/no-set-state": 0, 90 | "react/no-unknown-property": 1, 91 | "react/prefer-es6-class": 1, 92 | "react/prop-types": 1, 93 | "react/react-in-jsx-scope": 1, 94 | "react/require-default-props": 0, 95 | "react/self-closing-comp": 1, 96 | "react/sort-comp": 1, 97 | "react/jsx-wrap-multilines": 0, 98 | "react/no-array-index-key": 0, 99 | "import/extensions": 1, 100 | "import/prefer-default-export": 0, 101 | "import/no-extraneous-dependencies": 0, 102 | "import/no-named-as-default": 0, 103 | "react-hooks/rules-of-hooks": "error" 104 | }, 105 | "settings": { 106 | "import/resolver": { 107 | "node": { 108 | "paths": ["src"] 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/README.md: -------------------------------------------------------------------------------- 1 | # Example with React Router v4 2 | 3 | Example that shows usage of [mobx-session](../..) with React and React Router v4. It uses the `hasSession` property to determine if the user can access a specific route. 4 | 5 | ## Installation 6 | ### Install dependencies 7 | `npm install` 8 | 9 | ## Running the example 10 | `npm run start` 11 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "*": ["src/*"], 6 | "api/*": ["src/api/*"], 7 | "components/*": ["src/components/*"], 8 | "constants/*": ["src/constants/*"], 9 | "pages/*": ["src/pages/*"], 10 | "utils/*": ["src/utils/*"], 11 | "stores/*": ["src/stores/*"], 12 | } 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-for-mobx-session-with-react-router-v4", 3 | "version": "1.0.0", 4 | "description": "Example for the mobx-session library with react-router v4", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "babel-node ./node_modules/webpack-dev-server/bin/webpack-dev-server --open", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint src" 10 | }, 11 | "keywords": [], 12 | "author": "Rootstrap", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@babel/core": "^7.4.5", 16 | "@babel/node": "^7.4.5", 17 | "@babel/plugin-proposal-class-properties": "^7.4.4", 18 | "@babel/plugin-proposal-decorators": "^7.4.4", 19 | "@babel/preset-env": "^7.4.5", 20 | "@babel/preset-react": "^7.0.0", 21 | "babel-loader": "^8.0.6", 22 | "eslint": "4.19.1", 23 | "eslint-config-airbnb": "16.1.0", 24 | "eslint-plugin-import": "2.11.0", 25 | "eslint-plugin-jsx-a11y": "6.0.3", 26 | "eslint-plugin-react": "7.7.0", 27 | "eslint-plugin-react-hooks": "1.0.0", 28 | "file-loader": "^4.0.0", 29 | "html-webpack-plugin": "^3.2.0", 30 | "path": "^0.12.7", 31 | "webpack": "^4.33.0", 32 | "webpack-cli": "^3.3.4", 33 | "webpack-dev-server": "^3.7.1" 34 | }, 35 | "dependencies": { 36 | "mobx": "^5.10.1", 37 | "mobx-react": "^6.0.3", 38 | "mobx-session": "file:../../", 39 | "prop-types": "^15.7.2", 40 | "react": "^16.8.6", 41 | "react-dom": "^16.8.6", 42 | "react-router-dom": "^5.0.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/api/sessionApi.js: -------------------------------------------------------------------------------- 1 | const login = (user) => { 2 | const response = { 3 | token: '1a2b3c4d', 4 | data: { 5 | email: user.email, 6 | firstName: 'test', 7 | lastName: 'test' 8 | } 9 | }; 10 | return new Promise(resolve => setTimeout(resolve(response), 300)); 11 | }; 12 | 13 | const logout = () => new Promise(resolve => setTimeout(resolve, 300)); 14 | 15 | export default { login, logout }; 16 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { BrowserRouter as Router, Route } from 'react-router-dom'; 3 | import { observer } from 'mobx-react'; 4 | import SessionStore from 'mobx-session'; 5 | 6 | import PrivateRoute from './PrivateRoute'; 7 | import Home from './Home'; 8 | import Login from './Login'; 9 | 10 | const App = observer(() => ( 11 | 12 |
13 | { 14 | SessionStore.initialized && 15 | 16 | 17 | 18 | 19 | } 20 |
21 |
22 | )); 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Logout from 'components/Logout'; 4 | import SessionInfo from 'components/SessionInfo'; 5 | 6 | const Home = () => ( 7 |
8 | 9 | 10 |
11 | ); 12 | 13 | export default Home; 14 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/Input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { func, string } from 'prop-types'; 3 | 4 | const inputStyle = { 5 | padding: 10 6 | }; 7 | 8 | const Input = ({ onChange, type, name }) => ( 9 |
10 | 11 |
12 | onChange(value)} 17 | /> 18 |
19 | ); 20 | 21 | Input.propTypes = { 22 | onChange: func.isRequired, 23 | type: string.isRequired, 24 | name: string.isRequired 25 | }; 26 | 27 | export default Input; 28 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import { shape } from 'prop-types'; 4 | 5 | import UserStore from 'stores/UserStore'; 6 | import Input from 'components/Input'; 7 | 8 | const Login = ({ history }) => { 9 | const [email, updateEmail] = useState(''); 10 | const [password, updatePassword] = useState(''); 11 | 12 | const onSubmit = () => { 13 | if (email && password) { 14 | UserStore.login({ email, password }, history); 15 | } 16 | }; 17 | 18 | return ( 19 |
20 | 21 | 22 | 23 | 24 |
25 | ); 26 | }; 27 | 28 | Login.propTypes = { 29 | history: shape() 30 | }; 31 | 32 | export default withRouter(Login); 33 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/Logout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import UserStore from 'stores/UserStore'; 4 | 5 | const Logout = () => ( 6 |
7 | Try refreshing the page to see that the session is saved 8 |
9 | 10 |
11 | ); 12 | 13 | export default Logout; 14 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shape, bool, string, func } from 'prop-types'; 3 | import { Route, Redirect } from 'react-router-dom'; 4 | 5 | const PrivateRoute = ({ component, exact = false, path, authenticated }) => ( 6 | ( 10 | authenticated ? ( 11 | React.createElement(component, props) 12 | ) : ( 13 | 19 | ) 20 | )} 21 | /> 22 | ); 23 | 24 | PrivateRoute.propTypes = { 25 | component: func.isRequired, 26 | exact: bool, 27 | path: string.isRequired, 28 | authenticated: bool.isRequired, 29 | location: shape() 30 | }; 31 | 32 | export default PrivateRoute; 33 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/components/SessionInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import UserStore from 'stores/UserStore'; 5 | 6 | const infoStyles = { 7 | backgroundColor: 'lightgray', 8 | padding: 20, 9 | margin: 10 10 | }; 11 | 12 | const SessionInfo = observer(({ userInfo: { user } }) => ( 13 |
14 |

Current Session Info:

15 | { user ? 16 |
17 | email: { user.email }
18 | firstName: { user.firstName }
19 | lastName: { user.lastName }
20 |
21 | :

No session info

22 | } 23 |
24 |
25 | )); 26 | 27 | const ConnectedSessionInfo = () => ; 28 | 29 | export default ConnectedSessionInfo; 30 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/index.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from 'components/App'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ); 9 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/src/stores/UserStore.js: -------------------------------------------------------------------------------- 1 | import { extendObservable, runInAction } from 'mobx'; 2 | import SessionStore from 'mobx-session'; 3 | 4 | import sessionApi from 'api/sessionApi'; 5 | 6 | class Store { 7 | constructor() { 8 | SessionStore.initialize({ name: 'example-app' }); 9 | 10 | extendObservable(this, { 11 | user: null, 12 | loginError: false, 13 | logoutError: false, 14 | get loggedIn() { 15 | return this.user !== null && SessionStore.hasSession; 16 | } 17 | }); 18 | 19 | runInAction('Load user', async () => { 20 | this.user = await SessionStore.getSession(); 21 | }); 22 | } 23 | 24 | saveUser = async (session) => { 25 | await SessionStore.saveSession(session); 26 | runInAction('Save user', () => { 27 | this.user = session; 28 | }); 29 | } 30 | 31 | removeUser = () => { 32 | SessionStore.deleteSession(); 33 | runInAction('Logout user', () => { 34 | this.user = null; 35 | }); 36 | } 37 | 38 | login = async (user, history) => { 39 | try { 40 | runInAction('Init Login', () => { 41 | this.loginError = false; 42 | }); 43 | const { data } = await sessionApi.login(user); 44 | await this.saveUser(data); 45 | history.push('/'); 46 | } catch (error) { 47 | runInAction('Error Login', () => { 48 | this.loginError = error.errors; 49 | }); 50 | } 51 | } 52 | 53 | logout = async () => { 54 | try { 55 | runInAction('Init Logout', () => { 56 | this.logoutError = false; 57 | }); 58 | await sessionApi.logout(); 59 | this.removeUser(); 60 | } catch (error) { 61 | runInAction('Error Logout', () => { 62 | this.logoutError = error.errors; 63 | }); 64 | } 65 | } 66 | } 67 | 68 | const UserStore = new Store(); 69 | 70 | export default UserStore; 71 | -------------------------------------------------------------------------------- /examples/react-router-v4-example/webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import "@babel/polyfill"; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | 5 | module.exports = { 6 | entry: [ 7 | "@babel/polyfill", 8 | path.resolve(__dirname, 'src/index.js') 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'build'), 12 | filename: 'index.bundle.js' 13 | }, 14 | mode: process.env.NODE_ENV || 'development', 15 | resolve: { 16 | modules: [path.resolve(__dirname, 'src'), 'node_modules'] 17 | }, 18 | devServer: { 19 | contentBase: path.join(__dirname, 'src') 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | // this is so that we can compile any React, 25 | // ES6 and above into normal ES5 syntax 26 | test: /\.(js|jsx)$/, 27 | // we do not want anything from node_modules to be compiled 28 | exclude: /node_modules/, 29 | use: ['babel-loader'] 30 | }, 31 | { 32 | test: /\.(jpg|jpeg|png|gif|mp3|svg)$/, 33 | loaders: ['file-loader'] 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new HtmlWebpackPlugin({ 39 | template: path.join(__dirname, 'src', 'index.html') 40 | }) 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobx-session", 3 | "version": "1.0.0", 4 | "description": "Save your session with MobX, using IndexedDB, WebSQL, or localStorage", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "npm run clean && babel src --out-dir dist", 8 | "test": "jest /src/__tests__", 9 | "test:watch": "jest /src/__tests__ --watch", 10 | "test:cover": "npm run test -- --coverage", 11 | "lint": "eslint src", 12 | "clean": "rimraf dist", 13 | "prepublishOnly": "npm run build" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/rootstrap/mobx-session.git" 18 | }, 19 | "keywords": [ 20 | "mobx", 21 | "react", 22 | "session", 23 | "authentication", 24 | "react-router", 25 | "localStorage" 26 | ], 27 | "author": "Rootstrap", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/rootstrap/mobx-session/issues" 31 | }, 32 | "homepage": "https://github.com/rootstrap/mobx-session#readme", 33 | "devDependencies": { 34 | "@babel/cli": "^7.4.4", 35 | "@babel/core": "^7.4.5", 36 | "@babel/plugin-proposal-class-properties": "^7.4.4", 37 | "@babel/polyfill": "^7.4.4", 38 | "@babel/preset-env": "^7.4.5", 39 | "babel-core": "^6.26.3", 40 | "babel-eslint": "^10.0.1", 41 | "babel-jest": "^24.8.0", 42 | "babel-loader": "^8.0.6", 43 | "babel-preset-latest": "^6.24.1", 44 | "babel-preset-stage-1": "^6.24.1", 45 | "babel-register": "^6.26.0", 46 | "coveralls": "^3.0.4", 47 | "eslint": "^5.16.0", 48 | "eslint-plugin-import": "^2.17.3", 49 | "jest": "^24.8.0", 50 | "mobx": "^5.10.1", 51 | "rimraf": "^2.6.3", 52 | "webpack": "^4.33.0" 53 | }, 54 | "dependencies": { 55 | "localforage": "^1.7.3" 56 | }, 57 | "peerDependencies": { 58 | "mobx": "^5.10.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill"; 2 | 3 | import SessionStore from '../index'; 4 | 5 | const SESSION = { token: 'abc123' }; 6 | 7 | describe('SessionStore', () => { 8 | describe('when the session store has not yet been initialized', () => { 9 | describe('initialized', () => { 10 | it('should return false', () => { 11 | expect(SessionStore.initialized).toEqual(false); 12 | }); 13 | }); 14 | }); 15 | 16 | describe('when session store has been initialized', () => { 17 | beforeEach(async () => { 18 | await SessionStore.initialize(); 19 | await SessionStore.deleteSession(); 20 | }); 21 | 22 | describe('initialized', () => { 23 | it('should return true', () => { 24 | expect(SessionStore.initialized).toBe(true); 25 | }); 26 | }); 27 | 28 | describe('saveSession', () => { 29 | let session; 30 | 31 | beforeEach(async () => { 32 | await SessionStore.saveSession(SESSION); 33 | session = await SessionStore.getSession(); 34 | }); 35 | 36 | it('should have saved the passed data', () => { 37 | expect(session).toStrictEqual(SESSION); 38 | }); 39 | }); 40 | 41 | describe('deleteSession', () => { 42 | let session; 43 | 44 | beforeEach(async () => { 45 | await SessionStore.saveSession(SESSION); 46 | await SessionStore.deleteSession(); 47 | session = await SessionStore.getSession(); 48 | }); 49 | 50 | it('should have the session as null', () => { 51 | expect(session).toBe(null); 52 | }); 53 | }); 54 | 55 | describe('hasSession', () => { 56 | describe('when the session has been already saved', () => { 57 | beforeEach(async () => { 58 | await SessionStore.saveSession(SESSION); 59 | }); 60 | 61 | it('should return true', () => { 62 | expect(SessionStore.hasSession).toBe(true); 63 | }); 64 | }); 65 | 66 | describe('when the session has not been saved', () => { 67 | it('should return false', () => { 68 | expect(SessionStore.hasSession).toBe(false); 69 | }); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const SESSION_KEY = 'session'; 2 | export const DEFAULT_INSTANCE_NAME = 'mobx-session'; 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { extendObservable, runInAction } from 'mobx'; 2 | import { Storage } from './storage'; 3 | 4 | import { DEFAULT_INSTANCE_NAME, SESSION_KEY } from './constants'; 5 | 6 | class Store { 7 | constructor() { 8 | extendObservable(this, { 9 | hasSession: false, 10 | initialized: false, 11 | }); 12 | } 13 | 14 | initialize = async (options = { name: DEFAULT_INSTANCE_NAME }) => { 15 | Storage.initialize(options); 16 | const hasSession = await this.getSession() !== null; 17 | runInAction(() => { 18 | this.initialized = true; 19 | this.hasSession = hasSession; 20 | }); 21 | } 22 | 23 | saveSession = async (session) => { 24 | await Storage.set(SESSION_KEY, session); 25 | runInAction(() => { 26 | this.hasSession = true; 27 | }); 28 | } 29 | 30 | getSession = () => Storage.get(SESSION_KEY); 31 | 32 | deleteSession = () => { 33 | Storage.remove(SESSION_KEY); 34 | runInAction(() => { 35 | this.hasSession = false; 36 | }); 37 | } 38 | } 39 | 40 | export default new Store(); 41 | -------------------------------------------------------------------------------- /src/storage.js: -------------------------------------------------------------------------------- 1 | import localforage from 'localforage'; 2 | 3 | class StorageClass { 4 | constructor() { 5 | this.storage = null; 6 | } 7 | 8 | initialize(options) { 9 | this.storage = localforage.createInstance(options); 10 | } 11 | 12 | get(key) { 13 | return this.storage.getItem(key); 14 | } 15 | 16 | set(key, item) { 17 | if (item === undefined) { 18 | return; 19 | } 20 | this.storage.setItem(key, item); 21 | } 22 | 23 | remove(key) { 24 | this.storage.removeItem(key); 25 | } 26 | } 27 | 28 | export const Storage = new StorageClass(); 29 | --------------------------------------------------------------------------------