├── .babelrc ├── .eslintrc ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── IMPORTANT.md ├── LICENSE ├── README.md ├── __mocks__ ├── AuthenticatorMocks.js ├── Scatter.js ├── UALAccountInputProps.js ├── localStorageMock.js └── providerProps.js ├── __setup__ ├── .DS_Store ├── babel.config.js ├── config.jest.transform.js └── enzyme.config.js ├── __tests__ ├── UALAccountInput.js ├── UALAuthButton.js ├── UALBox.js ├── UALErrorMessage.js ├── UALLearnMore.js ├── UALProvider.js └── withUAL.js ├── examples ├── .babelrc ├── .eslintrc ├── .gitignore ├── IMPORTANT.md ├── LICENSE ├── README.md ├── default.env ├── package.json ├── server │ └── template.html ├── src │ └── ButtonWebViewReact.tsx ├── tsconfig.json ├── webpack.config.react.js └── yarn.lock ├── jest.config.js ├── package.json ├── src ├── components │ ├── authentication │ │ ├── UALAccountInput.js │ │ ├── UALAuthButton.js │ │ └── UALInstallAuth.js │ ├── info │ │ ├── UALErrorMessage.js │ │ └── UALLearnMore.js │ ├── misc │ │ ├── UALExitButton.js │ │ └── UALLoadingIcon.js │ ├── modal │ │ ├── UALBox.js │ │ ├── UALBoxParts.js │ │ └── UALContainer.js │ └── provider │ │ ├── UALContext.js │ │ ├── UALProvider.js │ │ └── withUAL.js ├── constants │ ├── authentication.js │ ├── box.js │ └── provider.js ├── i18n │ ├── en-US.js │ ├── index.js │ ├── resources.js │ └── translationHelpers.js ├── index.js ├── styles │ ├── authenticator.js │ ├── base.js │ ├── box.js │ ├── buttons │ │ ├── back.js │ │ ├── exit.js │ │ └── retry.js │ ├── container.js │ ├── error.js │ ├── info.js │ ├── input.js │ ├── installation.js │ ├── instructions.js │ ├── loader.js │ ├── mediaQuery.js │ ├── provider.js │ └── title.js ├── types.js └── utils │ └── index.js ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["@babel/preset-env", "@babel/preset-react"], 3 | plugins: ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-regenerator"] 4 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@blockone/blockone" 3 | } 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Change Description 5 | 6 | 7 | 8 | ## API Changes 9 | - [ ] API Changes 10 | 11 | 12 | 13 | 14 | ## Documentation Additions 15 | - [ ] Documentation Additions 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #environment 2 | .env 3 | 4 | #npm we use yarn.lock 5 | package-lock.json 6 | 7 | coverage/* 8 | 9 | # editors 10 | # Intellij and idea based editors 11 | .idea 12 | 13 | # VS Code 14 | .vscode/* 15 | !.vscode/settings.json 16 | !.vscode/tasks.json 17 | !.vscode/launch.json 18 | !.vscode/extensions.json 19 | 20 | ## Sublime Text 21 | # Cache files for Sublime Text 22 | *.tmlanguage.cache 23 | *.tmPreferences.cache 24 | *.stTheme.cache 25 | 26 | # Workspace files are user-specific 27 | *.sublime-workspace 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Logs 40 | logs 41 | *.log 42 | npm-debug.log* 43 | yarn-debug.log* 44 | yarn-error.log* 45 | *.out 46 | 47 | # Runtime data 48 | pids 49 | *.pid 50 | *.seed 51 | *.pid.lock 52 | 53 | # Dependency directories 54 | node_modules/ 55 | jspm_packages/ 56 | 57 | # Optional npm cache directory 58 | .npm 59 | 60 | # Optional eslint cache 61 | .eslintcache 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # next.js build output 73 | .next 74 | 75 | # Typescript 76 | dist/ 77 | dist-web/ 78 | build/ 79 | 80 | # Dev Server Static Output 81 | public/ 82 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !/dist/**/* 3 | !README.md 4 | !package.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '12.14.1' 5 | stages: 6 | - test 7 | jobs: 8 | include: 9 | - stage: test 10 | name: "Lint and Test" 11 | script: 12 | - yarn lint 13 | - yarn test 14 | notifications: 15 | webhooks: 16 | secure: "IHXWWZ3+OySgNnnWOo8KK4VkvHPd3m0YmXe9JykRZ+tFNXDCo1z0mM5f+tgg+e2h5ZMqd61c+qAN36qExzaV9lfUxyIOR0786AS9elJz0xrOyrxlVETgxKWZy3qlFhPPZtxhxlPJPVQglaQi0W7ol3m/SnAgRYu3Ua+s1hJcaJq+WK9q+1bRhzr3TZj0KuGTCqvZ28j3tFrYfl5aLArbKda861KKpjmuiARIy73ggdOEfFUgXebTBTsmMh7UFvbw+6fb4HGO6bP/MtrDQafZE5h6DtwiqobcJ6Fn8F5qsCxBsUpn1XXCmavy2+NezcvXpyY7+G0caMm5rQLFvioZMv+ryu4U/QJRN7+4AGottRCVAv7iZX3AtdxhJtqdMtfLHaZv2um9qWMMiLfQ/yRxPHYIdMYnfV3U06LQZbtS5OoW4fFGEJFAcMiibs+ahPDXlCqnerDMrRDkYG/vPEE7TPhXm1w4NiiO1oANI0zTNeO8MvGjg1LC/Lr6nGS/YevuvHG4OH5ctIvC5IJoNbIC1d5YBcWQsKpmHXDD4EtOt8KiiVH2YOptrp5foV7Z0XVrVUJtwWsZm0cbz2AKL2AiiPythOXs13EdqQUWExPpJ9VA8f+2wU3QY1pe85tUkm8cNohBd8uub4plG8LfagtxGt5NFTcLete+uBxd8jHHBPU=" 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to UAL Renderer for ReactJS 2 | 3 | Interested in contributing? That's awesome! Here are some guidelines to get started quickly and easily: 4 | 5 | - [Reporting An Issue](#reporting-an-issue) 6 | - [Bug Reports](#bug-reports) 7 | - [Feature Requests](#feature-requests) 8 | - [Change Requests](#change-requests) 9 | - [Working on UAL Renderer for ReactJS](#working-on-ual-renderer-for-reactjs) 10 | - [Feature Branches](#feature-branches) 11 | - [Submitting Pull Requests](#submitting-pull-requests) 12 | - [Testing and Quality Assurance](#testing-and-quality-assurance) 13 | - [Conduct](#conduct) 14 | - [Contributor License & Acknowledgments](#contributor-license--acknowledgments) 15 | - [References](#references) 16 | 17 | ## Reporting An Issue 18 | 19 | If you're about to raise an issue because you think you've found a problem with UAL Renderer for ReactJS, or you'd like to make a request for a new feature in the codebase, or any other reason… please read this first. 20 | 21 | The GitHub issue tracker is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests), and [submitting pull requests](#submitting-pull-requests), but please respect the following restrictions: 22 | 23 | * Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone has already reported your problem or requested your idea. 24 | 25 | * Please **be civil**. Keep the discussion on topic and respect the opinions of others. See also our [Contributor Code of Conduct](#contributor-code-of-conduct). 26 | 27 | ### Bug Reports 28 | 29 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you! 30 | 31 | Guidelines for bug reports: 32 | 33 | 1. **Use the GitHub issue search** — check if the issue has already been 34 | reported. 35 | 36 | 1. **Check if the issue has been fixed** — look for [closed issues in the 37 | current milestone](https://github.com/EOSIO/ual-react/issues?q=is%3Aissue+is%3Aclosed) or try to reproduce it 38 | using the latest `develop` branch. 39 | 40 | A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment and relevant tests that demonstrate the failure. 41 | 42 | [Report a bug](https://github.com/EOSIO/ual-react/issues/new?title=Bug%3A) 43 | 44 | ### Feature Requests 45 | 46 | Feature requests are welcome. Before you submit one be sure to have: 47 | 48 | 1. **Use the GitHub search** and check the feature hasn't already been requested. 49 | 1. Take a moment to think about whether your idea fits with the scope and aims of the project. 50 | 1. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this feature. Please provide as much detail and context as possible, this means explaining the use case and why it is likely to be common. 51 | 52 | ### Change Requests 53 | 54 | Change requests cover both architectural and functional changes to how UAL Renderer for ReactJS works. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to: 55 | 56 | 1. **Use the GitHub search** and check someone else didn't get there first 57 | 1. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure this shouldn't really be 58 | a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than what's already there? 59 | 60 | ## Working on UAL Renderer for ReactJS 61 | 62 | Code contributions are welcome and encouraged! If you are looking for a good place to start, check out the [good first issue](https://github.com/EOSIO/ual-react/labels/good%20first%20issue) label in GitHub issues. 63 | 64 | Also, please follow these guidelines when submitting code: 65 | 66 | ### Feature Branches 67 | 68 | To get it out of the way: 69 | 70 | - **[develop](https://github.com/EOSIO/ual-react/tree/develop)** is the development branch. All work on the next release happens here so you should generally branch off `develop`. Do **NOT** use this branch for a production site. 71 | - **[master](https://github.com/EOSIO/ual-react)** contains the latest release of UAL Renderer for ReactJS. This branch may be used in production. Do **NOT** use this branch to work on UAL Renderer for ReactJS's source. 72 | 73 | ### Submitting Pull Requests 74 | 75 | Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#reporting-an-issue) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. 76 | 77 | ### Testing and Quality Assurance 78 | 79 | Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and don't know where to start, checking out and testing a pull request is one of the most useful things you could do. 80 | 81 | Essentially, [check out the latest develop branch](#working-on-ual-react), take it for a spin, and if you find anything odd, please follow the [bug report guidelines](#bug-reports) and let us know! 82 | 83 | ## Conduct 84 | 85 | While contributing, please be respectful and constructive, so that participation in our project is a positive experience for everyone. 86 | 87 | Examples of behavior that contributes to creating a positive environment include: 88 | - Using welcoming and inclusive language 89 | - Being respectful of differing viewpoints and experiences 90 | - Gracefully accepting constructive criticism 91 | - Focusing on what is best for the community 92 | - Showing empathy towards other community members 93 | 94 | Examples of unacceptable behavior include: 95 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 96 | - Trolling, insulting/derogatory comments, and personal or political attacks 97 | - Public or private harassment 98 | - Publishing others’ private information, such as a physical or electronic address, without explicit permission 99 | - Other conduct which could reasonably be considered inappropriate in a professional setting 100 | 101 | ## Contributor License & Acknowledgments 102 | 103 | Whenever you make a contribution to this project, you license your contribution under the same terms as set out in LICENSE, and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: 104 | 105 | ``` 106 | Developer Certificate of Origin 107 | Version 1.1 108 | 109 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 110 | 1 Letterman Drive 111 | Suite D4700 112 | San Francisco, CA, 94129 113 | 114 | Everyone is permitted to copy and distribute verbatim copies of this 115 | license document, but changing it is not allowed. 116 | 117 | 118 | Developer's Certificate of Origin 1.1 119 | 120 | By making a contribution to this project, I certify that: 121 | 122 | (a) The contribution was created in whole or in part by me and I 123 | have the right to submit it under the open source license 124 | indicated in the file; or 125 | 126 | (b) The contribution is based upon previous work that, to the best 127 | of my knowledge, is covered under an appropriate open source 128 | license and I have the right under that license to submit that 129 | work with modifications, whether created in whole or in part 130 | by me, under the same open source license (unless I am 131 | permitted to submit under a different license), as indicated 132 | in the file; or 133 | 134 | (c) The contribution was provided directly to me by some other 135 | person who certified (a), (b) or (c) and I have not modified 136 | it. 137 | 138 | (d) I understand and agree that this project and the contribution 139 | are public and that a record of the contribution (including all 140 | personal information I submit with it, including my sign-off) is 141 | maintained indefinitely and may be redistributed consistent with 142 | this project or the open source license(s) involved. 143 | ``` 144 | 145 | ## References 146 | 147 | * Overall CONTRIB adapted from https://github.com/mathjax/MathJax/blob/master/CONTRIBUTING.md 148 | * Conduct section adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 149 | -------------------------------------------------------------------------------- /IMPORTANT.md: -------------------------------------------------------------------------------- 1 | # Important Notice 2 | 3 | We (block.one and its affiliates) make available EOSIO and other software, updates, patches and documentation (collectively, Software) on a voluntary basis as a member of the EOSIO community. A condition of you accessing any Software, websites, articles, media, publications, documents or other material (collectively, Material) is your acceptance of the terms of this important notice. 4 | 5 | ## Software 6 | We are not responsible for ensuring the overall performance of Software or any related applications. Any test results or performance figures are indicative and will not reflect performance under all conditions. Software may contain components that are open sourced and subject to their own licenses; you are responsible for ensuring your compliance with those licenses. 7 | 8 | We make no representation, warranty, guarantee or undertaking in respect of Software, whether expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall we 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. 9 | 10 | Wallets and related components are complex software that require the highest levels of security. If incorrectly built or used, they may compromise users’ private keys and digital assets. Wallet applications and related components should undergo thorough security evaluations before being used. Only experienced developers should work with such Software. 11 | 12 | Material is not made available to any person or entity that is the subject of sanctions administered or enforced by any country or government or otherwise designated on any list of prohibited or restricted parties (including but not limited to the lists maintained by the United Nations Security Council, the U.S. Government, the European Union or its Member States, or other applicable government authority) or organized or resident in a country or territory that is the subject of country-wide or territory-wide sanctions. You represent and warrant that neither you nor any party having a direct or indirect beneficial interest in you or on whose behalf you are acting as agent or nominee is such a person or entity and you will comply with all applicable import, re-import, sanctions, anti-boycott, export, and re-export control laws and regulations. If this is not accurate or you do not agree, then you must immediately cease accessing our Material and delete all copies of Software. 13 | 14 | Any person using or offering Software in connection with providing software, goods or services to third parties shall advise such third parties of this important notice, including all limitations, restrictions and exclusions of liability. 15 | 16 | ## Trademarks 17 | Block.one, EOSIO, EOS, the heptahedron and associated logos and related marks are our trademarks. Other trademarks referenced in Material are the property of their respective owners. 18 | 19 | ## Third parties 20 | Any reference in Material to any third party or third-party product, resource or service is not an endorsement or recommendation by Block.one. We are not responsible for, and disclaim any and all responsibility and liability for, your use of or reliance on any of these resources. Third-party resources may be updated, changed or terminated at any time, so information in Material may be out of date or inaccurate. 21 | 22 | ## Forward-looking statements 23 | Please note that in making statements expressing Block.one’s vision, we do not guarantee anything, and all aspects of our vision are subject to change at any time and in all respects at Block.one’s sole discretion, with or without notice. We call these “forward-looking statements”, which includes statements on our website and in other Material, other than statements of historical facts, such as statements regarding EOSIO’s development, expected performance, and future features, or our business strategy, plans, prospects, developments and objectives. These statements are only predictions and reflect Block.one’s current beliefs and expectations with respect to future events; they are based on assumptions and are subject to risk, uncertainties and change at any time. 24 | 25 | We operate in a rapidly changing environment and new risks emerge from time to time. Given these risks and uncertainties, you are cautioned not to rely on these forward-looking statements. Actual results, performance or events may differ materially from what is predicted in the forward-looking statements. Some of the factors that could cause actual results, performance or events to differ materially from the forward-looking statements include, without limitation: technical feasibility and barriers; market trends and volatility; continued availability of capital, financing and personnel; product acceptance; the commercial success of any new products or technologies; competition; government regulation and laws; and general economic, market or business conditions. 26 | 27 | All statements are valid only as of the date of first posting and Block.one is under no obligation to, and expressly disclaims any obligation to, update or alter any statements, whether as a result of new information, subsequent events or otherwise. Nothing in any Material constitutes technological, financial, investment, legal or other advice, either in general or with regard to any particular situation or implementation. Please consult with experts in appropriate areas before implementing or utilizing anything contained in Material. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2019 block.one and its contributors. All rights reserved. 2 | 3 | The MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UAL Renderer for ReactJS 2 | 3 | This library provides a React renderer around the [Universal Authenticator Library](https://github.com/EOSIO/universal-authenticator-library). 4 | 5 | ![EOSIO Labs](https://img.shields.io/badge/EOSIO-Labs-5cb3ff.svg) 6 | 7 | # About EOSIO Labs 8 | 9 | EOSIO Labs repositories are experimental. Developers in the community are encouraged to use EOSIO Labs repositories as the basis for code and concepts to incorporate into their applications. Community members are also welcome to contribute and further develop these repositories. Since these repositories are not supported by Block.one, we may not provide responses to issue reports, pull requests, updates to functionality, or other requests from the community, and we encourage the community to take responsibility for these. 10 | 11 | ## Getting Started 12 | #### With ``yarn`` 13 | ```bash 14 | yarn add ual-reactjs-renderer 15 | ``` 16 | Then, install the authenticators that you wish to use... 17 | ```bash 18 | yarn add ual-scatter ual-lynx 19 | ``` 20 | #### With ``npm`` 21 | ```bash 22 | npm i ual-reactjs-renderer 23 | ``` 24 | Then, install the authenticators that you wish to use... 25 | ```bash 26 | npm i ual-scatter ual-lynx 27 | ``` 28 | 29 | ## Basic Usage 30 | ```javascript 31 | import React from 'react' 32 | import ReactDOM from 'react-dom' 33 | import { UALProvider, withUAL } from 'ual-reactjs-renderer' 34 | import { Scatter } from 'ual-scatter' 35 | import { Lynx } from 'ual-lynx' 36 | 37 | import { MyApp } from './MyApp' 38 | 39 | const myChain = { 40 | chainId: MY_CHAIN_ID, 41 | rpcEndpoints: [{ 42 | protocol: MY_CHAIN_PROTOCOL, 43 | host: MY_CHAIN_HOST, 44 | port: MY_CHAIN_PORT, 45 | }] 46 | } 47 | 48 | const scatter = new Scatter([myChain], { appName: 'My App' }) 49 | const lynx = new Lynx([myChain], { appName: 'My App' }) 50 | 51 | const MyUALConsumer = withUAL(MyApp) 52 | 53 | ReactDOM.render( 54 | 55 | 56 | , 57 | document.getElementById('ual-app') 58 | ) 59 | ``` 60 | 61 | ## Examples 62 | A small example is provided in the [examples](https://github.com/EOSIO/ual-reactjs-renderer/tree/develop/examples) folder. 63 | 64 | ## Environment Set Up 65 | **A one-time environment setup is required prior to development.** The following commands provides a quick starting point. Make sure you are in the ``examples/`` directory. 66 | ```bash 67 | cd examples 68 | cp default.env .env 69 | ``` 70 | Now that you have an ``.env`` file, you can set environment variables for your chain particulars. Note that this file will not be version-controlled, nor should it be. 71 | The default settings for the file are... 72 | ``` 73 | CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f 74 | RPC_PROTOCOL=http 75 | RPC_HOST=localhost 76 | RPC_PORT=8888 77 | ``` 78 | These values are taken from the local chain created by following the [Developer Portal node set up instructions](https://developers.eos.io/eosio-home/docs/getting-the-software). _(Note: if this is your first time following the tutorial you will need to install the eosio binaries [here](https://developers.eos.io/eosio-home/docs/setting-up-your-environment))_ These can be edited according to the requirements of your project if you have a different chain set up. They will be used as the chain data in the example app. 79 | *See the [Basic Example App for UAL with ReactJS](https://github.com/EOSIO/ual-reactjs-renderer/tree/develop/examples) for more details.* 80 | 81 | ## Development 82 | After you set up your environment you can begin development. Make sure you are back in the ``/`` directory of the ``ual-reactjs-renderer`` package. 83 | ```bash 84 | yarn 85 | yarn link 86 | yarn build -w 87 | ``` 88 | 89 | In a duplicate terminal tab, enter the following commands: 90 | ```bash 91 | cd examples 92 | yarn link ual-reactjs-renderer 93 | yarn 94 | yarn example 95 | ``` 96 | 97 | Open a browser at `localhost:3000` to see a running instance of the example. 98 | 99 | *It is recommended to have at least two terminal tabs running so that `yarn build -w` and `yarn example` can run simultaneously creating an environment where changes are immediately reflected in the browser.* 100 | 101 | ## Contributing 102 | 103 | [Contributing Guide](./CONTRIBUTING.md) 104 | 105 | [Code of Conduct](./CONTRIBUTING.md#conduct) 106 | 107 | ## License 108 | 109 | [MIT](./LICENSE) 110 | 111 | ## Important 112 | 113 | See [LICENSE](./LICENSE) for copyright and license terms. 114 | 115 | All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT.md) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. 116 | -------------------------------------------------------------------------------- /__mocks__/AuthenticatorMocks.js: -------------------------------------------------------------------------------- 1 | const baseAuth = { 2 | getStyle: () => ({ 3 | icon: '', 4 | background: '', 5 | textColor: '', 6 | text: '', 7 | }), 8 | getError: () => null, 9 | } 10 | 11 | export const availableAuth = { 12 | ...baseAuth, 13 | isLoading: () => false, 14 | isErrored: () => false, 15 | } 16 | 17 | export const loadingAuth = { 18 | ...baseAuth, 19 | isLoading: () => true, 20 | isErrored: () => false, 21 | } 22 | 23 | export const erroredAuth = { 24 | ...baseAuth, 25 | isLoading: () => false, 26 | isErrored: () => true, 27 | } 28 | -------------------------------------------------------------------------------- /__mocks__/Scatter.js: -------------------------------------------------------------------------------- 1 | import { Authenticator } from 'universal-authenticator-library' 2 | 3 | const ScatterJS = { 4 | scatter: { 5 | connect: (appName) => { 6 | if (appName === 'My Working App') { 7 | return true 8 | } 9 | return Promise.resolve(false) 10 | }, 11 | }, 12 | } 13 | 14 | const scatter = { 15 | logout: () => {}, 16 | } 17 | 18 | class UALScatterError { 19 | constructor(message, type, error) { 20 | this.message = message 21 | this.type = type 22 | this.error = error 23 | this.source = 'Scatter' 24 | } 25 | } 26 | 27 | class ScatterUser { 28 | constructor(chain, scatter) { 29 | this.chain = chain 30 | this.scatter = scatter 31 | } 32 | 33 | getKeys() { 34 | if (this.scatter) { 35 | return Promise.resolve('keys!') 36 | } 37 | throw new Error() 38 | } 39 | } 40 | 41 | export class Scatter extends Authenticator { 42 | constructor(chains, options = { appName: '' }) { 43 | super(chains) 44 | this.appName = options.appName 45 | this.scatterIsLoading = false 46 | this.initError = null 47 | this.scatter = false 48 | } 49 | 50 | async init() { 51 | this.scatterIsLoading = false 52 | if (!await ScatterJS.scatter.connect(this.appName)) { 53 | this.initError = new UALScatterError('Error occurred while connecting', 54 | 'initialization', 55 | null) 56 | 57 | this.scatterIsLoading = false 58 | 59 | return 60 | } 61 | this.scatter = scatter 62 | this.scatterIsLoading = false 63 | } 64 | 65 | isLoading() { 66 | return false 67 | } 68 | 69 | isErrored() { 70 | return !!this.initError 71 | } 72 | 73 | getError() { 74 | return this.initError 75 | } 76 | 77 | getStyle() { 78 | return { 79 | icon: 'logo', 80 | text: 'Scatter', 81 | textColor: 'white', 82 | background: '#62D0FD', 83 | } 84 | } 85 | 86 | shouldRender() { 87 | return true 88 | } 89 | 90 | shouldAutoLogin() { 91 | return false 92 | } 93 | 94 | requiresGetKeyConfirmation() { 95 | return false 96 | } 97 | 98 | async login() { 99 | try { 100 | for (const chain of this.chains) { 101 | const user = new ScatterUser(chain, this.scatter) 102 | await user.getKeys() 103 | this.users.push(user) 104 | } 105 | 106 | return this.users 107 | } catch (e) { 108 | throw new UALScatterError( 109 | 'Unable to login', 110 | 'login', 111 | e, 112 | ) 113 | } 114 | } 115 | 116 | async logout() { 117 | try { 118 | this.scatter.logout() 119 | } catch (error) { 120 | throw new UALScatterError('Error occurred during logout', 121 | 'logout', 122 | error) 123 | } 124 | } 125 | 126 | async shouldRequestAccountName() { 127 | return true 128 | } 129 | 130 | getName() { 131 | return 'scatter' 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /__mocks__/UALAccountInputProps.js: -------------------------------------------------------------------------------- 1 | const baseProps = { 2 | authenticator: { 3 | getStyle: jest.fn().mockReturnValue('rgb(100,100,100)') 4 | }, 5 | submitAccountForLogin: jest.fn(), 6 | } 7 | 8 | export const loadingProps = { 9 | ...baseProps, 10 | loading: true, 11 | } 12 | 13 | export const availableProps = { 14 | ...baseProps, 15 | loading: false, 16 | } 17 | -------------------------------------------------------------------------------- /__mocks__/localStorageMock.js: -------------------------------------------------------------------------------- 1 | export const localStorageMock = { 2 | getItem: key => this[key], 3 | setItem: (key, val) => { 4 | this[key] = val 5 | }, 6 | clear: () => { 7 | Object.keys(this).forEach(key => delete this[key]) 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /__mocks__/providerProps.js: -------------------------------------------------------------------------------- 1 | import { Scatter } from 'ual-scatter' 2 | 3 | const EXAMPLE_ENV = { 4 | CHAIN_ID: '1', 5 | RPC_PROTOCOL: 'https', 6 | RPC_HOST: 'dummy.net', 7 | RPC_PORT: 4000, 8 | } 9 | 10 | const exampleNet = { 11 | chainId: EXAMPLE_ENV.CHAIN_ID, 12 | rpcEndpoints: [{ 13 | protocol: EXAMPLE_ENV.RPC_PROTOCOL, 14 | host: EXAMPLE_ENV.RPC_HOST, 15 | port: Number(EXAMPLE_ENV.RPC_PORT), 16 | }], 17 | } 18 | 19 | export const providerProps = { 20 | chains: [exampleNet], 21 | authenticators: [new Scatter([exampleNet], { appName: 'My Working App' })], 22 | appName: 'My app', 23 | } 24 | -------------------------------------------------------------------------------- /__setup__/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EOSIO/ual-reactjs-renderer/91f5f223b067185e2fbc55cadbf6af3bdb80ff27/__setup__/.DS_Store -------------------------------------------------------------------------------- /__setup__/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react'], 3 | plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-transform-regenerator'], 4 | } 5 | -------------------------------------------------------------------------------- /__setup__/config.jest.transform.js: -------------------------------------------------------------------------------- 1 | const babelConfig = require('./babel.config') 2 | 3 | module.exports = require('babel-jest').createTransformer(babelConfig) 4 | -------------------------------------------------------------------------------- /__setup__/enzyme.config.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import { configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | 5 | configure({ adapter: new Adapter() }) 6 | -------------------------------------------------------------------------------- /__tests__/UALAccountInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow, mount } from 'enzyme' 3 | import { loadingProps, availableProps } from 'UALAccountInputProps' 4 | import { UALAccountInput, StyledInput } from '../src/components/authentication/UALAccountInput' 5 | import { UALLoadingIcon } from '../src/components/misc/UALLoadingIcon' 6 | 7 | describe('UALAccountInput', () => { 8 | it('renders an input', () => { 9 | const wrapper = shallow() 10 | expect(wrapper.find(UALLoadingIcon).length).toBe(0) 11 | expect(wrapper.find(StyledInput).length).toBe(1) 12 | }) 13 | 14 | it('renders a loading icon in place of the input when loading prop is true', () => { 15 | const wrapper = shallow() 16 | expect(wrapper.find(StyledInput).length).toBe(0) 17 | expect(wrapper.find(UALLoadingIcon).length).toBe(1) 18 | }) 19 | 20 | it('submits valid account name for login on click', () => { 21 | const wrapper = mount() 22 | wrapper.find({ role: 'button' }).simulate('click') 23 | expect(availableProps.submitAccountForLogin).not.toHaveBeenCalled() 24 | wrapper.setState({ accountInput: 'example' }) 25 | wrapper.find({ role: 'button' }).simulate('click') 26 | expect(availableProps.submitAccountForLogin).toHaveBeenCalled() 27 | }) 28 | 29 | describe('validates input client side', () => { 30 | let wrapper 31 | const input = { 32 | target: { 33 | value: '7example', 34 | }, 35 | } 36 | 37 | beforeAll(() => { 38 | wrapper = shallow() 39 | }) 40 | 41 | it('by forcing first character to be lowercase letter or numbers 1 - 5', () => { 42 | wrapper.instance().updateButtonWithInput(input) 43 | expect(wrapper.state().accountInput).not.toEqual('.example') 44 | input.target.value = '7example' 45 | wrapper.instance().updateButtonWithInput(input) 46 | expect(wrapper.state().accountInput).not.toEqual('7example') 47 | input.target.value = '3example' 48 | wrapper.instance().updateButtonWithInput(input) 49 | expect(wrapper.state().accountInput).toEqual('3example') 50 | input.target.value = 'example' 51 | wrapper.instance().updateButtonWithInput(input) 52 | expect(wrapper.state().accountInput).toEqual('example') 53 | }) 54 | 55 | it('by allowing a . to be used after first character', () => { 56 | input.target.value = 'e.xample' 57 | wrapper.instance().updateButtonWithInput(input) 58 | expect(wrapper.state().accountInput).toEqual('e.xample') 59 | }) 60 | 61 | it('by enforcing a max character length of 12', () => { 62 | input.target.value = 'lessthan12' 63 | wrapper.instance().updateButtonWithInput(input) 64 | expect(wrapper.state().accountInput).toEqual('lessthan12') 65 | input.target.value = 'morethantwelve' 66 | wrapper.instance().updateButtonWithInput(input) 67 | expect(wrapper.state().accountInput).not.toEqual('morethantwelve') 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /__tests__/UALAuthButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { FaChevronRight, FaDownload } from 'react-icons/fa' 4 | import { IoMdInformationCircleOutline } from 'react-icons/io' 5 | import { availableAuth, loadingAuth, erroredAuth } from 'AuthenticatorMocks' 6 | import { UALLoadingIcon } from '../src/components/misc/UALLoadingIcon' 7 | import { UALAuthButton } from '../src/components/authentication/UALAuthButton' 8 | import { boxTitles } from '../src/constants/box' 9 | 10 | describe('UALAuthButton', () => { 11 | const onAuthenticatorSelect = jest.fn() 12 | const onErroredState = jest.fn() 13 | const onRequestInstall = jest.fn() 14 | const instructions = boxTitles.NORMAL 15 | const baseProps = { 16 | onAuthenticatorSelect, 17 | onErroredState, 18 | onRequestInstall, 19 | instructions, 20 | } 21 | const unavailableProps = { 22 | ...baseProps, 23 | instructions: boxTitles.ERROR, 24 | } 25 | 26 | 27 | it('shows loading state when authenticator is loading', () => { 28 | const wrapper = shallow() 33 | expect(wrapper.state().button).toEqual('loading') 34 | expect(wrapper.find(UALLoadingIcon).length).toBe(1) 35 | }) 36 | 37 | it('shows available state when authenticator is available', () => { 38 | const wrapper = shallow() 43 | expect(wrapper.state().button).toEqual('available') 44 | expect(wrapper.find(FaChevronRight).length).toBe(1) 45 | }) 46 | 47 | it('shows error state when authenticator has errored', () => { 48 | const wrapper = shallow() 53 | expect(wrapper.state().button).toEqual('errored') 54 | expect(wrapper.find(IoMdInformationCircleOutline).length).toBe(1) 55 | }) 56 | 57 | it('shows unavailable state when instructions prop reflects error screen', () => { 58 | const wrapper = shallow() 63 | expect(wrapper.state().button).toEqual('unavailable') 64 | expect(wrapper.find(FaDownload).length).toBe(1) 65 | }) 66 | 67 | it('attempts authentication when clicked', () => { 68 | const wrapper = shallow() 73 | wrapper.find({ role: 'button' }).simulate('click') 74 | expect(onAuthenticatorSelect).toHaveBeenCalled() 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /__tests__/UALBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | import { providerProps } from 'providerProps' 4 | import { UALError, UALErrorType } from 'universal-authenticator-library' 5 | import { UALBox } from '../src/components/modal/UALBox' 6 | import { UALProvider } from '../src/index' 7 | import { UALErrorMessage } from '../src/components/info/UALErrorMessage' 8 | import { UALLoadingIcon } from '../src/components/misc/UALLoadingIcon' 9 | import { UALAuthButton } from '../src/components/authentication/UALAuthButton' 10 | import { UALAccountInput } from '../src/components/authentication/UALAccountInput' 11 | 12 | describe('UALBox', () => { 13 | let wrapper 14 | 15 | beforeEach(() => { 16 | wrapper = mount() 17 | }) 18 | 19 | it('displays an error message when UALProvider catches an error in its state', () => { 20 | wrapper.setState({ 21 | error: new UALError('Sample Error', UALErrorType.Initialization, new Error('Sample Error'), 'UAL'), 22 | }) 23 | expect(wrapper.find(UALBox).find(UALErrorMessage).length).toBe(1) 24 | }) 25 | 26 | it('displays loader when UALProvider is loading', () => { 27 | wrapper.setState({ 28 | loading: true, 29 | }) 30 | expect(wrapper.find(UALBox).find(UALLoadingIcon).length).toBe(1) 31 | }) 32 | 33 | it('renders an authenticator button for each authenticator in UALProvider availableAuthenticators', () => { 34 | expect(wrapper.find(UALBox).find(UALAuthButton).length).toBe(1) 35 | const { availableAuthenticators } = wrapper.state() 36 | availableAuthenticators.push(availableAuthenticators[0]) 37 | wrapper.setState({ 38 | availableAuthenticators, 39 | }) 40 | expect(wrapper.find(UALBox).find(UALAuthButton).length).toBe(2) 41 | }) 42 | 43 | it('renders an accountInput when showAccountInput is true', () => { 44 | const { availableAuthenticators } = wrapper.state() 45 | wrapper.setState({ 46 | showAccountInput: true, 47 | authenticator: availableAuthenticators[0], 48 | }) 49 | expect(wrapper.find(UALBox).find(UALAccountInput).length).toBe(1) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /__tests__/UALErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'enzyme' 3 | import { UALError, UALErrorType } from 'universal-authenticator-library' 4 | import { UALErrorMessage } from '../src/components/info/UALErrorMessage' 5 | 6 | describe('UALErrorMessage', () => { 7 | const errorA = new UALError('This is a message', UALErrorType.Initialization, null, 'UAL') 8 | const errorB = new UALError('This is another message', UALErrorType.Initialization, null, 'UAL') 9 | const wrapperA = render() 10 | const wrapperB = render() 11 | 12 | it('displays the error message', () => { 13 | expect(wrapperA.text().trim()).toEqual('This is a message') 14 | expect(wrapperB.text().trim()).toEqual('This is another message') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /__tests__/UALLearnMore.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IoMdInformationCircleOutline, IoMdCloseCircleOutline } from 'react-icons/io' 3 | 4 | import { shallow } from 'enzyme' 5 | import { UALLearnMore } from '../src/components/info/UALLearnMore' 6 | 7 | describe('UALLearnMore', () => { 8 | let wrapper 9 | 10 | beforeAll(() => { 11 | wrapper = shallow() 12 | }) 13 | 14 | it('loads in a collapsed state with info icon', () => { 15 | expect(wrapper.find(IoMdInformationCircleOutline).length).toBe(1) 16 | }) 17 | 18 | it('expands on info icon click', () => { 19 | wrapper.instance().toggleMoreInfo() 20 | expect(wrapper.find(IoMdCloseCircleOutline).length).toBe(1) 21 | }) 22 | 23 | it('collapses on close icon click', () => { 24 | wrapper.instance().toggleMoreInfo() 25 | expect(wrapper.find(IoMdInformationCircleOutline).length).toBe(1) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /__tests__/UALProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { providerProps } from 'providerProps' 4 | import { localStorageMock } from 'localStorageMock' 5 | import { UALBox } from '../src/components/modal/UALBox' 6 | import { UALProvider } from '../src/index' 7 | import { DEFAULT_STATUS } from '../src/constants/provider' 8 | 9 | jest.useFakeTimers(); 10 | 11 | describe('UALProvider', () => { 12 | describe('has a modal prop', () => { 13 | it('that renders a modal when it is true', () => { 14 | const wrapper = shallow(
) 15 | expect(wrapper.find(UALBox).length).toBe(1) 16 | }) 17 | 18 | it('that does not render a modal when false', () => { 19 | const wrapper = shallow(
) 20 | expect(wrapper.find(UALBox).length).toBe(0) 21 | }) 22 | }) 23 | 24 | describe('has a componentDidMount method', () => { 25 | let wrapper 26 | beforeAll(() => { 27 | global.localStorage = localStorageMock 28 | wrapper = shallow(
) 29 | }) 30 | 31 | afterEach(() => { 32 | global.localStorage.clear() 33 | }) 34 | 35 | it('that fetches all available authenticators', () => { 36 | const spy = jest.spyOn(wrapper.instance(), 'fetchAuthenticators') 37 | wrapper.instance().componentDidMount() 38 | expect(spy).toHaveBeenCalled() 39 | }) 40 | 41 | it('that attempts auto-login if authenticator type is in localStorage and not invalidated', async () => { 42 | localStorage.setItem('UALAccountName', 'Example') 43 | localStorage.setItem('UALLoggedInAuthType', 'Scatter') 44 | const invalidateAt = new Date(); 45 | invalidateAt.setSeconds(invalidateAt.getSeconds() + 100); 46 | window.localStorage.setItem('UALInvalidateAt', invalidateAt) 47 | 48 | const spy = jest.spyOn(wrapper.instance(), 'getAuthenticatorInstance') 49 | wrapper.instance().componentDidMount() 50 | expect(spy).toHaveBeenCalled() 51 | }) 52 | 53 | it('that does not attempt to auto-login if localStorage is empty', async () => { 54 | const spy = jest.spyOn(wrapper.instance(), 'getAuthenticatorInstance') 55 | wrapper.instance().componentDidMount() 56 | expect(spy).not.toHaveBeenCalled() 57 | }) 58 | 59 | it('that does not attempt to auto-login if localStorage is outdated', async () => { 60 | localStorage.setItem('UALAccountName', 'Example') 61 | localStorage.setItem('UALLoggedInAuthType', 'Scatter') 62 | const invalidateAt = new Date(); 63 | invalidateAt.setSeconds(invalidateAt.getSeconds() - 1); 64 | window.localStorage.setItem('UALInvalidateAt', invalidateAt) 65 | 66 | const spy = jest.spyOn(wrapper.instance(), 'getAuthenticatorInstance') 67 | wrapper.instance().componentDidMount() 68 | expect(spy).not.toHaveBeenCalled() 69 | }) 70 | }) 71 | 72 | describe('has a fetchAuthenticators method', () => { 73 | let wrapper 74 | 75 | beforeEach(() => { 76 | wrapper = shallow(
) 77 | }) 78 | 79 | it('that sets the availableAuthenticators with all authenticators that should render', () => { 80 | const { authenticators } = wrapper.state() 81 | wrapper.setState({ 82 | availableAuthenticators: [], 83 | }) 84 | expect(wrapper.state().availableAuthenticators.length).toBe(0) 85 | wrapper.instance().fetchAuthenticators(authenticators, authenticators[0]) 86 | expect(wrapper.state().availableAuthenticators.length).toBe(1) 87 | }) 88 | 89 | it('that automatically attempts authentication if autoLogin is available and not loading', () => { 90 | const { authenticators } = wrapper.state() 91 | const spy = jest.spyOn(wrapper.state(), 'authenticateWithoutAccountInput') 92 | const autoLoginAuth = authenticators[0] 93 | autoLoginAuth.isLoading = jest.fn().mockReturnValue(false) 94 | wrapper.instance().fetchAuthenticators(authenticators, autoLoginAuth) 95 | jest.advanceTimersByTime(1000) 96 | expect(spy).toHaveBeenCalled() 97 | }) 98 | 99 | it('that delays authentication if autoLogin is available but still loading', () => { 100 | const { authenticators } = wrapper.state() 101 | const spy = jest.spyOn(wrapper.state(), 'authenticateWithoutAccountInput') 102 | const autoLoginAuth = authenticators[0] 103 | autoLoginAuth.isLoading = jest.fn().mockReturnValue(true) 104 | wrapper.instance().fetchAuthenticators(authenticators, autoLoginAuth) 105 | expect(spy).not.toHaveBeenCalled() 106 | autoLoginAuth.isLoading = jest.fn().mockReturnValue(false) 107 | jest.advanceTimersByTime(1000) 108 | expect(spy).toHaveBeenCalled() 109 | }) 110 | }) 111 | 112 | describe('has a logout method', () => { 113 | let wrapper 114 | 115 | beforeAll(() => { 116 | global.localStorage = localStorageMock 117 | }) 118 | 119 | beforeEach(() => { 120 | wrapper = shallow(
) 121 | }) 122 | 123 | it('that resets the state to default status', () => { 124 | wrapper.setState(DEFAULT_STATUS) 125 | const defaultState = JSON.stringify(wrapper.state()) 126 | wrapper = shallow(
) 127 | expect(JSON.stringify(wrapper.state())).not.toEqual(defaultState) 128 | wrapper.state().logout() 129 | expect(JSON.stringify(wrapper.state())).toEqual(defaultState) 130 | }) 131 | 132 | it('that calls the fullLogout method if the activeAuthenticator state property is set', () => { 133 | const activeAuthenticator = wrapper.state().availableAuthenticators[0] 134 | const spy = jest.spyOn(wrapper.instance(), 'fullLogout') 135 | wrapper.instance().componentDidMount() 136 | wrapper.setState({ 137 | activeAuthenticator, 138 | }) 139 | wrapper.state().logout() 140 | expect(spy).toHaveBeenCalled() 141 | }) 142 | 143 | it('that calls the clearCache method', () => { 144 | const activeAuthenticator = wrapper.state().availableAuthenticators[0] 145 | const spy = jest.spyOn(wrapper.instance(), 'clearCache') 146 | wrapper.instance().componentDidMount() 147 | wrapper.setState({ 148 | activeAuthenticator, 149 | }) 150 | wrapper.state().logout() 151 | expect(spy).toHaveBeenCalled() 152 | }) 153 | 154 | it('that clears the localStorage', () => { 155 | localStorage.setItem('UALAccountName', 'Example') 156 | localStorage.setItem('UALLoggedInAuthType', 'Scatter') 157 | const invalidateAt = new Date(); 158 | invalidateAt.setSeconds(invalidateAt.getSeconds() - 1); 159 | window.localStorage.setItem('UALInvalidateAt', invalidateAt) 160 | const activeAuthenticator = wrapper.state().availableAuthenticators[0] 161 | wrapper.setState({ 162 | activeAuthenticator, 163 | }) 164 | expect(localStorage.hasOwnProperty('UALAccountName')).toBe(true) 165 | expect(localStorage.hasOwnProperty('UALLoggedInAuthType')).toBe(true) 166 | expect(localStorage.hasOwnProperty('UALInvalidateAt')).toBe(true) 167 | wrapper.state().logout() 168 | expect(localStorage.hasOwnProperty('UALAccountName')).toBe(false) 169 | expect(localStorage.hasOwnProperty('UALLoggedInAuthType')).toBe(false) 170 | expect(localStorage.hasOwnProperty('UALInvalidateAt')).toBe(false) 171 | }) 172 | }) 173 | }) 174 | -------------------------------------------------------------------------------- /__tests__/withUAL.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { mount } from 'enzyme' 3 | 4 | import { withUAL } from '../src/components/provider/withUAL' 5 | 6 | const mockContext = 'testContext' 7 | jest.mock('../src/components/provider/UALContext', () => ({ 8 | UALContext: { 9 | Consumer: ({ children }) => children(mockContext), 10 | } 11 | })) 12 | 13 | describe('withUAL', () => { 14 | let wrappedComponent 15 | let component 16 | let name 17 | 18 | beforeEach(() => { 19 | component = (props) => { 20 | return

Hello, {props.name}

; 21 | } 22 | name = 'testName' 23 | const WrappedComponent = withUAL(component) 24 | wrappedComponent = mount() 25 | }) 26 | 27 | it('sets the props', () => { 28 | expect(wrappedComponent.prop('name')).toBe(name) 29 | }) 30 | 31 | it('renders the wrapped component', () => { 32 | expect(wrappedComponent.find(component)).toHaveLength(1) 33 | }) 34 | 35 | fit('passes the context as the prop ual to the wrapped component', () => { 36 | expect(wrappedComponent.find(component).prop('ual')).toBe(mockContext) 37 | }) 38 | 39 | it('passes the props to the wrapped component', () => { 40 | expect(wrappedComponent.find(component).prop('name')).toBe(name) 41 | }) 42 | 43 | it('renders the props in the wrapped component', () => { 44 | expect(wrappedComponent.text()).toEqual(`Hello, ${name}`) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /examples/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : ["env", "react"] 3 | } -------------------------------------------------------------------------------- /examples/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@blockone/blockone" 3 | } 4 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | #environment 2 | .env 3 | 4 | # editors 5 | # Intellij and idea based editors 6 | .idea 7 | 8 | # VS Code 9 | .vscode/* 10 | !.vscode/settings.json 11 | !.vscode/tasks.json 12 | !.vscode/launch.json 13 | !.vscode/extensions.json 14 | 15 | ## Sublime Text 16 | # Cache files for Sublime Text 17 | *.tmlanguage.cache 18 | *.tmPreferences.cache 19 | *.stTheme.cache 20 | 21 | # Workspace files are user-specific 22 | *.sublime-workspace 23 | 24 | # OS generated files # 25 | ###################### 26 | .DS_Store 27 | .DS_Store? 28 | ._* 29 | .Spotlight-V100 30 | .Trashes 31 | ehthumbs.db 32 | Thumbs.db 33 | 34 | # Logs 35 | logs 36 | *.log 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | *.out 41 | 42 | # Runtime data 43 | pids 44 | *.pid 45 | *.seed 46 | *.pid.lock 47 | 48 | # Dependency directories 49 | node_modules/ 50 | jspm_packages/ 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional REPL history 59 | .node_repl_history 60 | 61 | # Output of 'npm pack' 62 | *.tgz 63 | !blockone-eosio-vault-js-api-0.0.12.tgz 64 | !blockone-eosio-vault-js-api-1.0.0.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # next.js build output 70 | .next 71 | 72 | # Typescript 73 | dist/ 74 | dist-web/ 75 | build/ 76 | 77 | # Dev Server Static Output 78 | public/ 79 | -------------------------------------------------------------------------------- /examples/IMPORTANT.md: -------------------------------------------------------------------------------- 1 | # Important Notice 2 | 3 | We (block.one and its affiliates) make available EOSIO and other software, updates, patches and documentation (collectively, Software) on a voluntary basis as a member of the EOSIO community. A condition of you accessing any Software, websites, articles, media, publications, documents or other material (collectively, Material) is your acceptance of the terms of this important notice. 4 | 5 | ## Software 6 | We are not responsible for ensuring the overall performance of Software or any related applications. Any test results or performance figures are indicative and will not reflect performance under all conditions. Software may contain components that are open sourced and subject to their own licenses; you are responsible for ensuring your compliance with those licenses. 7 | 8 | We make no representation, warranty, guarantee or undertaking in respect of Software, whether expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall we 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. 9 | 10 | Wallets and related components are complex software that require the highest levels of security. If incorrectly built or used, they may compromise users’ private keys and digital assets. Wallet applications and related components should undergo thorough security evaluations before being used. Only experienced developers should work with such Software. 11 | 12 | Material is not made available to any person or entity that is the subject of sanctions administered or enforced by any country or government or otherwise designated on any list of prohibited or restricted parties (including but not limited to the lists maintained by the United Nations Security Council, the U.S. Government, the European Union or its Member States, or other applicable government authority) or organized or resident in a country or territory that is the subject of country-wide or territory-wide sanctions. You represent and warrant that neither you nor any party having a direct or indirect beneficial interest in you or on whose behalf you are acting as agent or nominee is such a person or entity and you will comply with all applicable import, re-import, sanctions, anti-boycott, export, and re-export control laws and regulations. If this is not accurate or you do not agree, then you must immediately cease accessing our Material and delete all copies of Software. 13 | 14 | Any person using or offering Software in connection with providing software, goods or services to third parties shall advise such third parties of this important notice, including all limitations, restrictions and exclusions of liability. 15 | 16 | ## Trademarks 17 | Block.one, EOSIO, EOS, the heptahedron and associated logos and related marks are our trademarks. Other trademarks referenced in Material are the property of their respective owners. 18 | 19 | ## Third parties 20 | Any reference in Material to any third party or third-party product, resource or service is not an endorsement or recommendation by Block.one. We are not responsible for, and disclaim any and all responsibility and liability for, your use of or reliance on any of these resources. Third-party resources may be updated, changed or terminated at any time, so information in Material may be out of date or inaccurate. 21 | 22 | ## Forward-looking statements 23 | Please note that in making statements expressing Block.one’s vision, we do not guarantee anything, and all aspects of our vision are subject to change at any time and in all respects at Block.one’s sole discretion, with or without notice. We call these “forward-looking statements”, which includes statements on our website and in other Material, other than statements of historical facts, such as statements regarding EOSIO’s development, expected performance, and future features, or our business strategy, plans, prospects, developments and objectives. These statements are only predictions and reflect Block.one’s current beliefs and expectations with respect to future events; they are based on assumptions and are subject to risk, uncertainties and change at any time. 24 | 25 | We operate in a rapidly changing environment and new risks emerge from time to time. Given these risks and uncertainties, you are cautioned not to rely on these forward-looking statements. Actual results, performance or events may differ materially from what is predicted in the forward-looking statements. Some of the factors that could cause actual results, performance or events to differ materially from the forward-looking statements include, without limitation: technical feasibility and barriers; market trends and volatility; continued availability of capital, financing and personnel; product acceptance; the commercial success of any new products or technologies; competition; government regulation and laws; and general economic, market or business conditions. 26 | 27 | All statements are valid only as of the date of first posting and Block.one is under no obligation to, and expressly disclaims any obligation to, update or alter any statements, whether as a result of new information, subsequent events or otherwise. Nothing in any Material constitutes technological, financial, investment, legal or other advice, either in general or with regard to any particular situation or implementation. Please consult with experts in appropriate areas before implementing or utilizing anything contained in Material. 28 | -------------------------------------------------------------------------------- /examples/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2019 block.one and its contributors. All rights reserved. 2 | 3 | The MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Basic Example App for UAL with ReactJS 2 | 3 | This example demonstrates an implementation of the [Universal Authenticator Library Renderer for ReactJS](https://github.com/EOSIO/ual-reactjs-renderer) in a simple EOS transfer DAPP. It uses the [UAL for Scatter Authenticator](https://github.com/EOSIO/ual-scatter) and [UAL for Ledger Authenticator](https://github.com/EOSIO/ual-ledger) authenticators. 4 | 5 | ![EOSIO Labs](https://img.shields.io/badge/EOSIO-Labs-5cb3ff.svg) 6 | 7 | # About EOSIO Labs 8 | 9 | EOSIO Labs repositories are experimental. Developers in the community are encouraged to use EOSIO Labs repositories as the basis for code and concepts to incorporate into their applications. Community members are also welcome to contribute and further develop these repositories. Since these repositories are not supported by Block.one, we may not provide responses to issue reports, pull requests, updates to functionality, or other requests from the community, and we encourage the community to take responsibility for these. 10 | 11 | ## Setup 12 | ``` 13 | yarn 14 | cp default.env .env 15 | ``` 16 | 17 | The example application uses an environment configuration for the chain and rpc endpoints. Update the values in the .env file you created in the first step to point the application at your preferred chain and run the example. 18 | 19 | ## Environment Defaults 20 | The ``.env`` file that is generated from the first step has the following defaults: 21 | ``` 22 | CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f 23 | RPC_PROTOCOL=http 24 | RPC_HOST=localhost 25 | RPC_PORT=8888 26 | ``` 27 | 28 | ## The Transaction Object 29 | In the main file of this demo, ``src/ButtonWebViewReact.tsx``, a ``demoTransaction`` is initiated and dispatched following a successful authentication and user click action. This ``demoTransaction`` contains a simple transfer action - transferring a single EOS token from the authenticated user to an account named ``example``. 30 | ```javascript 31 | const demoTransaction = { 32 | actions: [{ 33 | account: 'eosio.token', 34 | name: 'transfer', 35 | authorization: [{ 36 | actor: '', // use account that was logged in 37 | permission: 'active', 38 | }], 39 | data: { 40 | from: '', // use account that was logged in 41 | to: 'example', 42 | quantity: '1.0000 EOS', 43 | memo: 'UAL rocks!', 44 | }, 45 | }], 46 | } 47 | ``` 48 | While the example ``TransactionApp`` will populate the ``actor`` and ``from`` fields with the authenticated user, it's up to you to update the ``to`` field with a valid user on your chain. 49 | 50 | ## Run the example 51 | ``` 52 | yarn example 53 | ``` 54 | 55 | Navigate to http://localhost:3000 to see the example application 56 | 57 | ## License 58 | 59 | [MIT](./LICENSE) 60 | 61 | ## Important 62 | 63 | See [LICENSE](./LICENSE) for copyright and license terms. 64 | 65 | All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT.md) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice. 66 | -------------------------------------------------------------------------------- /examples/default.env: -------------------------------------------------------------------------------- 1 | CHAIN_ID=cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f 2 | RPC_PROTOCOL=http 3 | RPC_HOST=localhost 4 | RPC_PORT=8888 5 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ual-reactjs-renderer-example", 3 | "version": "0.1.2", 4 | "main": "index.js", 5 | "author": { 6 | "name": "block.one", 7 | "url": "https://block.one/" 8 | }, 9 | "collaborators": [ 10 | "Nasser Abouelazm", 11 | "Mike Manfredi" 12 | ], 13 | "license": "MIT", 14 | "scripts": { 15 | "build": "rm -rf build && webpack --config webpack.config.react.js", 16 | "example": "webpack-dev-server --config webpack.config.react.js", 17 | "lint": "eslint --ext .js,.jsx,.ts,.tsx src" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.8.7", 21 | "@babel/node": "^7.8.7", 22 | "@babel/preset-env": "^7.8.7", 23 | "@babel/preset-react": "^7.8.3", 24 | "@blockone/eslint-config-blockone": "^3.0.0", 25 | "@types/react": "^15.0.4", 26 | "awesome-typescript-loader": "^5.2.1", 27 | "babel-loader": "^8.0.4", 28 | "babel-polyfill": "^6.26.0", 29 | "dotenv": "^8.2.0", 30 | "html-webpack-plugin": "^3.2.0", 31 | "typescript": "^3.8.3", 32 | "webpack": "^4.29.6", 33 | "webpack-cli": "^3.3.0", 34 | "webpack-dev-server": "^3.2.1" 35 | }, 36 | "dependencies": { 37 | "@babel/runtime": "7.8.7", 38 | "prop-types": "15.7.2", 39 | "react": "16.13.0", 40 | "react-dom": "16.13.0", 41 | "ual-ledger": "0.3.0", 42 | "ual-lynx": "0.4.0", 43 | "ual-reactjs-renderer": "../", 44 | "ual-scatter": "0.3.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/server/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Universal Authenticator Library - React 7 | 37 | 38 | 39 |

Universal Authenticator Library React

40 |
- TRANSFER DAPP DEMO -
41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /examples/src/ButtonWebViewReact.tsx: -------------------------------------------------------------------------------- 1 | import { Ledger } from 'ual-ledger' 2 | import { Lynx } from 'ual-lynx' 3 | import { Scatter } from 'ual-scatter' 4 | import { UALProvider, withUAL } from 'ual-reactjs-renderer' 5 | 6 | import { JsonRpc } from 'eosjs' 7 | import * as React from 'react' 8 | import ReactDOM from 'react-dom' 9 | 10 | const demoTransaction = { 11 | actions: [{ 12 | account: 'eosio.token', 13 | name: 'transfer', 14 | authorization: [{ 15 | actor: '', // use account that was logged in 16 | permission: 'active', 17 | }], 18 | data: { 19 | from: '', // use account that was logged in 20 | to: 'example', 21 | quantity: '1.0000 EOS', 22 | memo: 'UAL rocks!', 23 | }, 24 | }], 25 | } 26 | 27 | interface ExampleEnv { 28 | CHAIN_ID: string, 29 | RPC_PROTOCOL: string, 30 | RPC_HOST: string, 31 | RPC_PORT: string 32 | } 33 | 34 | interface TransactionProps { 35 | ual: any 36 | } 37 | 38 | interface TransactionState { 39 | activeUser: any 40 | accountName: string 41 | accountBalance: any 42 | rpc: JsonRpc 43 | } 44 | 45 | declare const EXAMPLE_ENV: ExampleEnv 46 | 47 | const exampleNet = { 48 | chainId: EXAMPLE_ENV.CHAIN_ID, 49 | rpcEndpoints: [{ 50 | protocol: EXAMPLE_ENV.RPC_PROTOCOL, 51 | host: EXAMPLE_ENV.RPC_HOST, 52 | port: Number(EXAMPLE_ENV.RPC_PORT), 53 | }] 54 | } 55 | 56 | const defaultState = { 57 | activeUser: null, 58 | accountName: '', 59 | accountBalance: null, 60 | } 61 | 62 | class TransactionApp extends React.Component { 63 | static displayName = 'TransactionApp' 64 | 65 | constructor(props: TransactionProps) { 66 | super(props) 67 | this.state = { 68 | ...defaultState, 69 | rpc: new JsonRpc(`${EXAMPLE_ENV.RPC_PROTOCOL}://${EXAMPLE_ENV.RPC_HOST}:${EXAMPLE_ENV.RPC_PORT}`) 70 | } 71 | this.updateAccountBalance = this.updateAccountBalance.bind(this) 72 | this.updateAccountName = this.updateAccountName.bind(this) 73 | this.renderTransferButton = this.renderTransferButton.bind(this) 74 | this.transfer = this.transfer.bind(this) 75 | this.renderModalButton = this.renderModalButton.bind(this) 76 | } 77 | 78 | public componentDidUpdate() { 79 | const { ual: { activeUser } } = this.props 80 | if (activeUser && !this.state.activeUser) { 81 | this.setState({ activeUser }, this.updateAccountName) 82 | } else if (!activeUser && this.state.activeUser) { 83 | this.setState(defaultState) 84 | } 85 | } 86 | 87 | public async updateAccountName(): Promise { 88 | try { 89 | const accountName = await this.state.activeUser.getAccountName() 90 | this.setState({ accountName }, this.updateAccountBalance) 91 | } catch (e) { 92 | console.warn(e) 93 | } 94 | } 95 | 96 | public async updateAccountBalance(): Promise { 97 | try { 98 | const account = await this.state.rpc.get_account(this.state.accountName) 99 | const accountBalance = account.core_liquid_balance 100 | this.setState({ accountBalance }) 101 | } catch (e) { 102 | console.warn(e) 103 | } 104 | } 105 | 106 | public async transfer() { 107 | const { accountName, activeUser } = this.state 108 | demoTransaction.actions[0].authorization[0].actor = accountName 109 | demoTransaction.actions[0].data.from = accountName 110 | try { 111 | await activeUser.signTransaction(demoTransaction, { broadcast: true }) 112 | await this.updateAccountBalance() 113 | } catch (error) { 114 | console.warn(error) 115 | } 116 | } 117 | 118 | public renderModalButton() { 119 | return ( 120 |

121 | Show UAL Modal 125 |

126 | ) 127 | } 128 | 129 | public renderTransferButton() { 130 | return ( 131 |

132 | 133 | {'Transfer 1 eos to example'} 134 | 135 |

136 | ) 137 | } 138 | 139 | public renderLogoutBtn = () => { 140 | const { ual: { activeUser, activeAuthenticator, logout } } = this.props 141 | if (!!activeUser && !!activeAuthenticator) { 142 | return ( 143 |

144 | 145 | {'Logout'} 146 | 147 |

148 | ) 149 | } 150 | } 151 | 152 | public render() { 153 | const { ual: { activeUser } } = this.props 154 | const { accountBalance, accountName } = this.state 155 | const modalButton = !activeUser && this.renderModalButton() 156 | const loggedIn = accountName ? `Logged in as ${accountName}` : '' 157 | const myBalance = accountBalance ? `Balance: ${accountBalance}` : '' 158 | const transferBtn = accountBalance && this.renderTransferButton() 159 | return ( 160 |
161 | {modalButton} 162 |

{loggedIn}

163 |

{myBalance}

164 | {transferBtn} 165 | {this.renderLogoutBtn()} 166 |
167 | ) 168 | } 169 | } 170 | 171 | const TestAppConsumer = withUAL(TransactionApp) 172 | 173 | TestAppConsumer.displayName = 'TestAppConsumer' 174 | 175 | const appName = 'My App' 176 | const lynx = new Lynx([exampleNet]) 177 | const ledger = new Ledger([exampleNet]) 178 | const scatter = new Scatter([exampleNet], { appName }) 179 | 180 | ReactDOM.render( 181 | 182 | 183 | , 184 | document.getElementById('ual-app') as HTMLElement, 185 | ) 186 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "rootDir": "../../", 6 | "outDir": "./dist", 7 | "strict": true, 8 | "noImplicitAny": false, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | "allowJs": true, 16 | "jsx": "react", 17 | "skipLibCheck": true 18 | }, 19 | "lib": ["esnext"], 20 | "include": [ 21 | "src/**/*.tsx", 22 | "examples/**/*" 23 | ], 24 | "exclude": [ 25 | "src/**/*.test.tsx", 26 | "public", 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/webpack.config.react.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const path = require('path') 4 | const fs = require('fs') 5 | 6 | require('dotenv').config() 7 | 8 | module.exports = { 9 | mode: "development", 10 | entry: [ 11 | "babel-polyfill", 12 | path.join(__dirname, "src/ButtonWebViewReact.tsx"), 13 | ], 14 | output: { 15 | path: path.join(__dirname, 'build'), 16 | filename: "main_bundle.js", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.tsx?$/, 22 | loader: 'awesome-typescript-loader', 23 | exclude: /node_modules/, 24 | options: { 25 | transpileOnly: true, 26 | babelrc: true, 27 | } 28 | }, 29 | { 30 | test: /\.(js)$/, 31 | exclude: /node_modules/, 32 | include: [ 33 | '/node_modules/@blockone/universal-authenticator-library', 34 | path.join(__dirname, '../') 35 | ], 36 | loader: "babel-loader", 37 | query: { 38 | presets: ['@babel/preset-env', '@babel/preset-react'], 39 | plugins: ['@babel/plugin-proposal-class-properties'], 40 | } 41 | 42 | }, 43 | 44 | ] 45 | }, 46 | devServer: { 47 | port: 3000, 48 | contentBase: path.join(__dirname, 'build') 49 | }, 50 | plugins: [ 51 | function () { 52 | this.plugin('done', function (stats) { 53 | if (!fs.existsSync('.env')) { 54 | console.error('\n\nERROR: No .env file found... Did you copy default.env to .env?\n\n'); 55 | process.exit(1); 56 | } 57 | }); 58 | }, 59 | new webpack.DefinePlugin({ 60 | 'EXAMPLE_ENV': { 61 | 'CHAIN_ID': JSON.stringify(process.env.CHAIN_ID), 62 | 'RPC_PROTOCOL': JSON.stringify(process.env.RPC_PROTOCOL), 63 | 'RPC_HOST': JSON.stringify(process.env.RPC_HOST), 64 | 'RPC_PORT': JSON.stringify(process.env.RPC_PORT) 65 | } 66 | }), 67 | new HtmlWebpackPlugin({ 68 | template: "./server/template.html", 69 | path: path.join(__dirname, 'build'), 70 | filename: "index.html" 71 | }) 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | collectCoverageFrom: ['src/**/*.{js,jsx,mjs}'], 4 | coverageDirectory: 'coverage', 5 | moduleFileExtensions: ['js', 'json', 'jsx'], 6 | moduleNameMapper: { 7 | 'ual-scatter': '/__mocks__/Scatter.js', 8 | 'providerProps': '/__mocks__/providerProps.js', 9 | 'localStorageMock': '/__mocks__/localStorageMock.js', 10 | 'UALAccountInputProps': '/__mocks__/UALAccountInputProps.js', 11 | 'AuthenticatorMocks': '/__mocks__/AuthenticatorMocks.js', 12 | }, 13 | setupFiles: ['/__setup__/enzyme.config.js'], 14 | testEnvironment: 'jsdom', 15 | testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'], 16 | testPathIgnorePatterns: ['\\\\node_modules\\\\'], 17 | testURL: 'http://localhost', 18 | transformIgnorePatterns: ['/node_modules/'], 19 | verbose: false, 20 | transform: { 21 | '^.+\\.js$': '/__setup__/config.jest.transform.js' 22 | }, 23 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ual-reactjs-renderer", 3 | "version": "0.3.1", 4 | "main": "dist/index.js", 5 | "author": { 6 | "name": "block.one", 7 | "url": "https://block.one/" 8 | }, 9 | "collaborators": [ 10 | "Nasser Abouelazm", 11 | "Mike Manfredi" 12 | ], 13 | "license": "MIT", 14 | "scripts": { 15 | "build": "rm -rf dist && babel src --out-dir dist --source-maps", 16 | "lint": "eslint --ext .js,.jsx,.ts,.tsx src", 17 | "test": "jest", 18 | "prepare": "yarn build", 19 | "docs": "jsdoc src -r -d docs" 20 | }, 21 | "dependencies": { 22 | "i18next": "14.0.1", 23 | "i18next-browser-languagedetector": "2.2.4", 24 | "prop-types": "15.7.2", 25 | "react": "16.13.0", 26 | "react-dom": "16.13.0", 27 | "react-icons": "3.9.0", 28 | "react-tooltip": "3.9.2", 29 | "styled-components": "4.4.1", 30 | "universal-authenticator-library": "0.3.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.8.4", 34 | "@babel/core": "^7.8.7", 35 | "@babel/plugin-proposal-class-properties": "^7.8.3", 36 | "@babel/plugin-transform-regenerator": "^7.8.7", 37 | "@babel/preset-env": "^7.8.7", 38 | "@babel/preset-react": "^7.8.3", 39 | "@blockone/eslint-config-blockone": "^3.0.0", 40 | "@types/i18next": "^12.1.0", 41 | "@types/i18next-browser-languagedetector": "^2.0.1", 42 | "@types/jest": "^25.1.4", 43 | "@types/react": "15.0.4", 44 | "babel-jest": "^25.1.0", 45 | "babel-polyfill": "^6.26.0", 46 | "enzyme": "^3.11.0", 47 | "enzyme-adapter-react-16": "^1.15.2", 48 | "enzyme-to-json": "^3.4.4", 49 | "eslint": "^6.8.0", 50 | "jest": "^25.1.0", 51 | "jest-enzyme": "^7.1.2", 52 | "jsdoc": "^3.5.5", 53 | "typescript": "^3.8.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/authentication/UALAccountInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import i18n from '../../i18n' 5 | 6 | import { UALLoadingIcon } from '../misc/UALLoadingIcon' 7 | 8 | import { inputWrapper, inputStyle, buttonEnabled, buttonDisabled } from '../../styles/input' 9 | import { buttonHover } from '../../styles/authenticator' 10 | import { buttonText } from '../../styles/installation' 11 | 12 | export const StyledInput = styled.input` 13 | &::-webkit-input-placeholder { 14 | color: rgba(255,255,255,0.5); 15 | } 16 | ` 17 | 18 | /** 19 | * Component for the account name input field. 20 | */ 21 | export class UALAccountInput extends Component { 22 | static displayName = 'UALAccountInput' 23 | 24 | constructor(props) { 25 | super(props) 26 | /** 27 | * @memberof UALAccountInput 28 | * @name state 29 | * @prop {string} accountInput - currently entered account input 30 | * @prop {Object} hoverStyle - additional button style on hover 31 | */ 32 | this.state = { 33 | accountInput: '', 34 | hoverStyle: {}, 35 | } 36 | /** 37 | * @memberof UALAccountInput 38 | * @name validator 39 | * @type {RegExp} 40 | * @desc used to validate account input against chain constraints 41 | */ 42 | this.validator = new RegExp(/[a-z1-5]{1}[.a-z1-5]{0,11}/) 43 | } 44 | 45 | componentDidMount() { 46 | setTimeout(() => { 47 | this.inputField.focus() 48 | }, 300) 49 | } 50 | 51 | /** 52 | * @method 53 | * @return {Void} 54 | * @param {Event} e 55 | */ 56 | submitFromKeyboard = (e) => { 57 | const { submitAccountForLogin, authenticator } = this.props 58 | const { accountInput } = this.state 59 | return (e.which === 13 || e.keyCode === 13) && submitAccountForLogin(accountInput, authenticator) 60 | } 61 | 62 | /** 63 | * @method 64 | * @return {Void} 65 | * @param {Event} e 66 | */ 67 | updateButtonWithInput = (e) => { 68 | const accountInput = e.target.value 69 | const isValid = accountInput.match(this.validator) && accountInput.match(this.validator)[0] === accountInput 70 | if (isValid || !accountInput.length) { 71 | this.setState({ accountInput }) 72 | } 73 | } 74 | 75 | /** 76 | * @method 77 | * @return {Void} 78 | */ 79 | activateGenericSize = () => { 80 | this.setState({ hoverStyle: {} }) 81 | } 82 | 83 | /** 84 | * @method 85 | * @return {Void} 86 | */ 87 | activateHoverSize = () => { 88 | if (this.state.accountInput !== '') { 89 | this.setState({ hoverStyle: buttonHover }) 90 | } 91 | } 92 | 93 | /** 94 | * @method 95 | * @return {ReactElement} 96 | */ 97 | renderInput = () => { 98 | const { accountInput, hoverStyle } = this.state 99 | const { submitAccountForLogin, authenticator } = this.props 100 | const buttonStyle = accountInput !== '' ? buttonEnabled : buttonDisabled 101 | const background = authenticator.getStyle().background 102 | return ( 103 |
104 | { this.inputField = input }} 113 | autoCapitalize='none' 114 | /> 115 |
accountInput && submitAccountForLogin(accountInput, authenticator)} 123 | > 124 |
{i18n.t('continue')}
125 |
126 |
127 | ) 128 | } 129 | 130 | render() { 131 | return !this.props.loading ? this.renderInput() : 132 | } 133 | } 134 | 135 | /** 136 | * @memberof UALAccountInput 137 | * @name props 138 | * @prop {Authenticator} authenticator - authenticator to enter account name for 139 | * @prop {function} submitAccountForLogin - attempts authentication using UAL context function 140 | * @prop {boolean} loading - loading state of authentication 141 | */ 142 | UALAccountInput.propTypes = { 143 | authenticator: PropTypes.object.isRequired, 144 | submitAccountForLogin: PropTypes.func.isRequired, 145 | loading: PropTypes.bool.isRequired, 146 | } 147 | -------------------------------------------------------------------------------- /src/components/authentication/UALAuthButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Tooltip from 'react-tooltip' 4 | import { FaChevronRight, FaDownload } from 'react-icons/fa' 5 | import { IoMdInformationCircleOutline } from 'react-icons/io' 6 | 7 | import { UALLoadingIcon } from '../misc/UALLoadingIcon' 8 | import { boxTitles } from '../../constants/box' 9 | 10 | import { 11 | authButton, 12 | buttonHover, 13 | authIcon, 14 | authIconWrapper, 15 | authText, 16 | authTextFont, 17 | chevron, 18 | errored, 19 | } from '../../styles/authenticator' 20 | 21 | import { buttonState, errorColors } from '../../constants/authentication' 22 | 23 | /** 24 | * Component that provides a button for logging in with a given Authenticator. 25 | */ 26 | export class UALAuthButton extends Component { 27 | static displayName = 'UALAuthButton' 28 | 29 | constructor(props) { 30 | super(props) 31 | let button = buttonState.UNAVAILABLE 32 | if (props.instructions === boxTitles.NORMAL) { 33 | button = buttonState.AVAILABLE 34 | button = props.authenticator.isErrored() ? buttonState.ERRORED : button 35 | button = props.authenticator.isLoading() ? buttonState.LOADING : button 36 | } 37 | /** 38 | * @memberof UALAuthButton 39 | * @name state 40 | * @prop {string} icon - icon associated with authenticator 41 | * @prop {string} background - hexidecimal button background color 42 | * @prop {string} textColor - hexidecimal button text color 43 | * @prop {string} text - button text 44 | * @prop {string} button - button status 45 | * @prop {Object} hoverStyle - additional button style on hover 46 | */ 47 | this.state = { 48 | ...props.authenticator.getStyle(), 49 | button, 50 | } 51 | /** 52 | * @memberof UALAuthButton 53 | * @name buttonStateChecker 54 | * @type {null|function} 55 | * @desc interval for checking button status during initialization 56 | */ 57 | this.buttonStateChecker = null 58 | } 59 | 60 | componentDidMount() { 61 | const { onErroredState } = this.props 62 | const { button } = this.state 63 | if (button === buttonState.LOADING) { 64 | this.startButtonStateChecker() 65 | } 66 | if (button === buttonState.ERRORED) { 67 | onErroredState() 68 | } 69 | } 70 | 71 | componentDidUpdate() { 72 | this.checkButtonAvailability() 73 | this.checkButtonStatusOnRetry() 74 | } 75 | 76 | componentWillUnmount() { 77 | clearInterval(this.buttonStateChecker) 78 | } 79 | 80 | /** 81 | * @method 82 | * @return {Void} 83 | */ 84 | checkButtonAvailability = () => { 85 | const { instructions } = this.props 86 | const { button } = this.state 87 | if (instructions === boxTitles.ERROR && button !== buttonState.UNAVAILABLE) { 88 | this.setState({ button: buttonState.UNAVAILABLE }) 89 | } 90 | } 91 | 92 | /** 93 | * @method 94 | * @return {Void} 95 | */ 96 | checkButtonStatusOnRetry = () => { 97 | const { instructions } = this.props 98 | const { button } = this.state 99 | if (instructions === boxTitles.NORMAL && button === buttonState.UNAVAILABLE) { 100 | this.setState({ button: buttonState.LOADING }, this.startButtonStateChecker) 101 | } 102 | } 103 | 104 | /** 105 | * @method 106 | * @return {Void} 107 | */ 108 | startButtonStateChecker = () => { 109 | const { authenticator, onErroredState } = this.props 110 | this.buttonStateChecker = setInterval(() => { 111 | if (!authenticator.isLoading() && !authenticator.isErrored()) { 112 | clearInterval(this.buttonStateChecker) 113 | this.setState({ button: buttonState.AVAILABLE }) 114 | } else if (authenticator.isErrored()) { 115 | clearInterval(this.buttonStateChecker) 116 | this.setState({ button: buttonState.ERRORED }, onErroredState()) 117 | } 118 | }, 250) 119 | } 120 | 121 | /** 122 | * @method 123 | * @return {Void} 124 | */ 125 | attemptAuthentication = () => { 126 | const { button } = this.state 127 | const { onAuthenticatorSelect, authenticator, onRequestInstall } = this.props 128 | if (button === buttonState.AVAILABLE) { 129 | onAuthenticatorSelect(authenticator) 130 | } 131 | if (button === buttonState.UNAVAILABLE) { 132 | onRequestInstall(authenticator) 133 | } 134 | } 135 | 136 | /** 137 | * @method 138 | * @return {Void} 139 | */ 140 | activateGenericSize = () => { 141 | const { button } = this.state 142 | if (button === buttonState.AVAILABLE || button === buttonState.UNAVAILABLE) { 143 | this.setState({ hoverStyle: {} }) 144 | } 145 | } 146 | 147 | /** 148 | * @method 149 | * @return {Void} 150 | */ 151 | activateHoverSize = () => { 152 | const { button } = this.state 153 | if (button === buttonState.AVAILABLE || button === buttonState.UNAVAILABLE) { 154 | this.setState({ hoverStyle: buttonHover }) 155 | } 156 | } 157 | 158 | /** 159 | * @method 160 | * @return {ReactElement} 161 | */ 162 | renderIcon = () => { 163 | const { button } = this.state 164 | switch (button) { 165 | case buttonState.LOADING: 166 | return 167 | case buttonState.ERRORED: 168 | return 169 | case buttonState.UNAVAILABLE: 170 | return 171 | default: 172 | return 173 | } 174 | } 175 | 176 | render() { 177 | const { icon, background, textColor, text, hoverStyle, button } = this.state 178 | const { authenticator } = this.props 179 | const trueBackground = button === buttonState.ERRORED ? errorColors.LIGHT_GREY 180 | : background 181 | const trueTextColor = button === buttonState.ERRORED ? errorColors.DARK_GREY 182 | : textColor 183 | const errorTooltip = authenticator.getError() ? authenticator.getError().message : '' 184 | const toolTip = errorTooltip.length > 0 && button !== buttonState.UNAVAILABLE && 185 | return ( 186 |
197 | {toolTip} 198 |
199 |
200 | {text} 201 | { this.renderIcon() } 202 |
203 |
204 | ) 205 | } 206 | } 207 | 208 | /** 209 | * @memberof UALAuthButton 210 | * @name props 211 | * @prop {string} instructions - current instructions on UAL modal 212 | * @prop {function} onAuthenticatorSelect - triggered when authenticator button is clicked 213 | * @prop {function} onRequestInstall - triggered when authenticator install button is clicked 214 | * @prop {function} onErroredState - triggered when authenticator initialization ends in error 215 | * @prop {Authenticator} authenticator - reference to current authenticator instance 216 | * @prop {number} index - authenticator's index in list 217 | */ 218 | UALAuthButton.propTypes = { 219 | instructions: PropTypes.string.isRequired, 220 | onAuthenticatorSelect: PropTypes.func.isRequired, 221 | onRequestInstall: PropTypes.func.isRequired, 222 | onErroredState: PropTypes.func.isRequired, 223 | authenticator: PropTypes.object.isRequired, 224 | index: PropTypes.number.isRequired, 225 | } 226 | -------------------------------------------------------------------------------- /src/components/authentication/UALInstallAuth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import i18n from '../../i18n' 5 | 6 | import { installButton, installButtonWrapper, buttonText } from '../../styles/installation' 7 | import { baseLink } from '../../styles/base' 8 | import { buttonHover } from '../../styles/authenticator' 9 | 10 | /** 11 | * Component for rendering the authenticator install screen 12 | */ 13 | export class UALInstallAuth extends Component { 14 | static displayName = 'UALInstallAuth' 15 | 16 | constructor(props) { 17 | super(props) 18 | /** 19 | * @memberof UALInstallAuth 20 | * @name state 21 | * @prop {Object} hoverStyle - additional button style on hover 22 | */ 23 | this.state = { 24 | hoverStyle: {}, 25 | } 26 | } 27 | 28 | /** 29 | * @method 30 | * @return {Void} 31 | */ 32 | activateGenericSize = () => { 33 | this.setState({ hoverStyle: {} }) 34 | } 35 | 36 | /** 37 | * @method 38 | * @return {Void} 39 | */ 40 | activateHoverSize = () => { 41 | this.setState({ hoverStyle: buttonHover }) 42 | } 43 | 44 | render() { 45 | const { hoverStyle } = this.state 46 | const { authenticator } = this.props 47 | const background = authenticator.getStyle().background 48 | return ( 49 | 68 | ) 69 | } 70 | } 71 | 72 | /** 73 | * @memberof UALInstallAuth 74 | * @name props 75 | * @prop {Authenticator} authenticator - authenticator from which to render an install button 76 | */ 77 | UALInstallAuth.propTypes = { 78 | authenticator: PropTypes.object.isRequired, 79 | } 80 | -------------------------------------------------------------------------------- /src/components/info/UALErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { UALError } from 'universal-authenticator-library' 4 | 5 | import { IoMdInformationCircleOutline } from 'react-icons/io' 6 | 7 | import { base } from '../../styles/base' 8 | import { errorMessage } from '../../styles/error' 9 | 10 | /** 11 | * @class 12 | * @name UALErrorMessage 13 | * @desc component for rendering error messages 14 | */ 15 | export const UALErrorMessage = ({ error: { message } }) => ( 16 |
17 |

18 | 19 | {' '} 20 | {message} 21 |

22 |
23 | ) 24 | 25 | UALErrorMessage.displayName = 'UALErrorMessage' 26 | 27 | /** 28 | * @memberof UALErrorMessage 29 | * @name props 30 | * @prop {UALError|Object} chains - list of chains the app supports 31 | */ 32 | UALErrorMessage.propTypes = { 33 | error: PropTypes.oneOfType([ 34 | PropTypes.instanceOf(UALError), 35 | PropTypes.object, 36 | ]).isRequired, 37 | } 38 | -------------------------------------------------------------------------------- /src/components/info/UALLearnMore.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { IoMdInformationCircleOutline, IoMdCloseCircleOutline } from 'react-icons/io' 3 | import i18n from '../../i18n' 4 | 5 | import { base } from '../../styles/base' 6 | import { 7 | learnMore, 8 | infoExpanded, 9 | learnMoreText, 10 | learnMoreButton, 11 | learnMoreIcon, 12 | } from '../../styles/info' 13 | 14 | /** 15 | * Component for rendering the "Learn More" text. 16 | */ 17 | export class UALLearnMore extends Component { 18 | static displayName = 'UALLearnMore' 19 | 20 | /** 21 | * @memberof UALLearnMore 22 | * @name state 23 | * @prop {boolean} [false] open - state of learn more text accordion 24 | */ 25 | constructor(props) { 26 | super(props) 27 | this.state = { open: false } 28 | } 29 | 30 | /** 31 | * @method 32 | * @return {Void} 33 | */ 34 | toggleMoreInfo = () => { 35 | const { open } = this.state 36 | this.setState({ open: !open }) 37 | } 38 | 39 | render() { 40 | const { open } = this.state 41 | const buttonMessage = open ? i18n.t('learnMoreAccept') : i18n.t('learnMore') 42 | const info = i18n.t('learnMoreText') 43 | const accordionStyles = open ? infoExpanded : {} 44 | const buttonIcon = open ? 45 | : 46 | return ( 47 |
48 |
49 |

50 | {info} 51 |

52 |
53 |

54 | 62 | {buttonIcon} 63 | {' '} 64 | {buttonMessage} 65 | 66 |

67 |
68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/misc/UALExitButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import { FaTimes } from 'react-icons/fa' 5 | 6 | import { exitWrapper, exit, exitHover } from '../../styles/buttons/exit' 7 | 8 | /** 9 | * Component for rendering a modal close button. 10 | */ 11 | export class UALExitButton extends Component { 12 | static displayName = 'UALExitButton' 13 | 14 | constructor(props) { 15 | super(props) 16 | /** 17 | * @memberof UALExitButton 18 | * @name state 19 | * @prop {Object} hoverStyle - additional button style on hover 20 | */ 21 | this.state = { 22 | hoverStyle: {}, 23 | } 24 | } 25 | 26 | /** 27 | * @method 28 | * @return {Void} 29 | */ 30 | scaleUp = () => { 31 | this.setState({ hoverStyle: exitHover }) 32 | } 33 | 34 | /** 35 | * @method 36 | * @return {Void} 37 | */ 38 | scaleDown = () => { 39 | this.setState({ hoverStyle: {} }) 40 | } 41 | 42 | render() { 43 | const { hideModal, isSecondaryStyle } = this.props 44 | const { hoverStyle } = this.state 45 | const buttonColor = isSecondaryStyle ? { color: 'white' } : {} 46 | return ( 47 |

48 | 57 | 58 | 59 |

60 | ) 61 | } 62 | } 63 | 64 | /** 65 | * @memberof UALExitButton 66 | * @name props 67 | * @prop {function} hideModal - from UAL, hides modal 68 | * @prop {boolean} isSecondaryStyle - whether or not button should be light 69 | */ 70 | UALExitButton.propTypes = { 71 | hideModal: PropTypes.func.isRequired, 72 | isSecondaryStyle: PropTypes.bool.isRequired, 73 | } 74 | -------------------------------------------------------------------------------- /src/components/misc/UALLoadingIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import { 5 | loadingIcon, 6 | loadingIconWithContainer, 7 | loadingElementOne, 8 | loadingElementTwo, 9 | loadingElementThree, 10 | loadingElementCSS, 11 | } from '../../styles/loader' 12 | 13 | /** 14 | * @class 15 | * @name UALLoadingIcon 16 | * @desc Component that renders a loading icon 17 | */ 18 | export const UALLoadingIcon = ({ withContainer }) => ( 19 |
20 |
21 |
22 |
23 | 24 |
25 | ) 26 | 27 | UALLoadingIcon.displayName = 'UALLoadingIcon' 28 | 29 | UALLoadingIcon.defaultProps = { 30 | withContainer: false, 31 | } 32 | 33 | /** 34 | * @memberof UALInstallAuth 35 | * @name props 36 | * @prop {boolean} [false] withAuthenticator - authenticator from which to render an install button 37 | */ 38 | UALLoadingIcon.propTypes = { 39 | withContainer: PropTypes.bool, 40 | } 41 | -------------------------------------------------------------------------------- /src/components/modal/UALBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import i18n from '../../i18n' 5 | 6 | import { UALContainer } from './UALContainer' 7 | import { withUAL } from '../provider/withUAL' 8 | import { UALBoxParts } from './UALBoxParts' 9 | 10 | import { box } from '../../styles/box' 11 | import { titleWrapper } from '../../styles/title' 12 | import { mediaQuery } from '../../styles/mediaQuery' 13 | 14 | import { boxTitles, installGuide, defaultBoxState } from '../../constants/box' 15 | 16 | /** 17 | * Component for rendering the box containing the AuthButtons, account input, and handling 18 | * basic preparatory authentication logic 19 | */ 20 | class UALBoxBase extends Component { 21 | static displayName = 'UALBoxBase' 22 | 23 | constructor(props) { 24 | super(props) 25 | this.state = defaultBoxState 26 | } 27 | 28 | componentDidMount() { 29 | const { ual: { activeUser, modal, hideModal } } = this.props 30 | if (activeUser && modal) { 31 | hideModal() 32 | } else { 33 | setTimeout(() => this.setState({ containerEnter: true }), 300) 34 | } 35 | } 36 | 37 | componentDidUpdate() { 38 | const { ual: { activeUser, modal, hideModal } } = this.props 39 | if (activeUser && modal) { 40 | hideModal() 41 | } 42 | } 43 | 44 | /** 45 | * Attempts authentication 46 | * 47 | * @method 48 | * @return {Void} 49 | * @param {Authenticator} authenticator 50 | */ 51 | authenticate = async (authenticator) => { 52 | const { ual: { authenticateWithoutAccountInput } } = this.props 53 | const needsAccountName = await authenticator.shouldRequestAccountName() 54 | if (needsAccountName) { 55 | this.authenticateWithAccountInput(authenticator) 56 | } else { 57 | authenticateWithoutAccountInput(authenticator) 58 | } 59 | } 60 | 61 | /** 62 | * Checks if all available authenticators have errored 63 | * 64 | * @method 65 | * @return {Void} 66 | */ 67 | checkAuthenticators = () => { 68 | const { availableAuthenticators } = this.props.ual 69 | const nonErroredAuthenticators = availableAuthenticators.filter(auth => !auth.isErrored()) 70 | if (!nonErroredAuthenticators.length) { 71 | this.setState({ 72 | instructions: boxTitles.ERROR, 73 | secondaryInstructions: installGuide, 74 | }) 75 | } 76 | } 77 | 78 | /** 79 | * Transitions to authenticator's account input screen 80 | * 81 | * @method 82 | * @return {Void} 83 | * @param {Authenticator} authenticator 84 | */ 85 | authenticateWithAccountInput = (authenticator) => { 86 | const loginState = { 87 | containerEnter: true, 88 | showAccountInput: true, 89 | showInstallScreen: false, 90 | instructions: i18n.t('enterUsername'), 91 | secondaryInstructions: '', 92 | } 93 | this.setState({ 94 | authenticator, 95 | containerExit: true, 96 | containerEnter: false, 97 | transitionForward: true, 98 | }, 99 | this.transitionToReset(loginState)) 100 | } 101 | 102 | /** 103 | * Transitions to authenticator's install screen 104 | * 105 | * @method 106 | * @return {Void} 107 | * @param {Authenticator} authenticator 108 | */ 109 | enterInstallScreen = (authenticator) => { 110 | const authName = authenticator.getStyle().text 111 | const secondaryInstructions = i18n.t('getStarted', { authName }) 112 | const installState = { 113 | containerEnter: true, 114 | showInstallScreen: true, 115 | showAccountInput: false, 116 | instructions: i18n.t('welcomeAccount', { authName }), 117 | secondaryInstructions, 118 | } 119 | this.setState({ 120 | authenticator, 121 | containerExit: true, 122 | containerEnter: false, 123 | transitionForward: true, 124 | }, 125 | this.transitionToReset(installState)) 126 | } 127 | 128 | /** 129 | * Returns to authenticator select screen 130 | * 131 | * @method 132 | * @return {Void} 133 | */ 134 | goBackToAuthSelect = () => { 135 | const { showInstallScreen } = this.state 136 | const instructions = showInstallScreen ? boxTitles.ERROR : boxTitles.NORMAL 137 | const secondaryInstructions = showInstallScreen ? installGuide : '' 138 | const previousSelectState = { 139 | containerEnter: true, 140 | showAccountInput: false, 141 | showInstallScreen: false, 142 | instructions, 143 | secondaryInstructions, 144 | } 145 | this.setState({ 146 | containerExit: true, 147 | containerEnter: false, 148 | transitionForward: false, 149 | }, 150 | this.transitionToReset(previousSelectState)) 151 | } 152 | 153 | /** 154 | * Attempts authentication 155 | * 156 | * @method 157 | * @return {Void} 158 | * @param {Authenticator} authenticator 159 | */ 160 | refreshBox = () => { 161 | const { restart } = this.props.ual 162 | this.setState({ ...defaultBoxState, containerEnter: true }, restart) 163 | } 164 | 165 | /** 166 | * @method 167 | * @return {Void} 168 | * @param {Object} nextState 169 | */ 170 | resetContainer = (nextState) => { 171 | this.setState({ containerExit: false, containerEnter: false }, this.transitionToComplete(nextState)) 172 | } 173 | 174 | /** 175 | * @method 176 | * @return {Void} 177 | * @param {Object} nextState 178 | */ 179 | transitionToReset = (nextState) => { 180 | setTimeout(() => { this.resetContainer(nextState) }, 300) 181 | } 182 | 183 | /** 184 | * @method 185 | * @return {Void} 186 | * @param {Object} nextState 187 | */ 188 | transitionToComplete = (nextState) => { 189 | setTimeout(() => { this.setState(nextState) }, 50) 190 | } 191 | 192 | render() { 193 | const { 194 | containerEnter, 195 | containerExit, 196 | transitionForward, 197 | ...state 198 | } = this.state 199 | const { ual } = this.props 200 | const app = { ...state, ...ual } 201 | const background = UALBoxParts.boxBackground(app) 202 | return ( 203 |
204 | {UALBoxParts.exitButton(app)} 205 |
206 | {UALBoxParts.boxTitle(app)} 207 |
208 | 209 | {UALBoxParts.secondaryInstructions(app, this.refreshBox)} 210 | {UALBoxParts.mainContent(app, this.authenticate, this.checkAuthenticators, this.enterInstallScreen)} 211 | {UALBoxParts.errorMessage(ual)} 212 | {UALBoxParts.backButton(app, this.goBackToAuthSelect)} 213 | {UALBoxParts.learnMore(app)} 214 | 215 | 216 |
217 | ) 218 | } 219 | } 220 | 221 | export const UALBox = withUAL(UALBoxBase) 222 | 223 | UALBox.displayName = 'UALBox' 224 | 225 | /** 226 | * @typeDef ual 227 | * @desc the ual context that UALBox consumes 228 | * @prop {Chain[]} chains - chain list from props 229 | * @prop {Authenticator[]} authenticators - authenticator instances from props 230 | * @prop {Authenticator[]} availableAuthenticators - available authenticator list 231 | * @prop {boolean} loading - loading state of UAL 232 | * @prop {User} activeUser - logged in user 233 | * @prop {string} message - message, if any, accompanying current UAL state 234 | * @prop {function} broadcastStatus - dispatches a provider state update 235 | * @prop {function} hideModal - hides the modal 236 | * @prop {boolean} modal - whether or not show modal, initialized via props 237 | * @prop {string} appName - name of app 238 | * @prop {function} logout - logs user out of authenticator and resets UAL state 239 | * @prop {function} restart - resets all available authenticators and resets UAL state 240 | * @prop {UALError|null} error - captured error if any 241 | * @prop {function} authenticateWithoutAccountInput - attempts authentication with an authenticator not requiring account input 242 | * @prop {function} submitAccountForLogin - attempts authentication 243 | */ 244 | /** 245 | * @memberof UALBoxBase 246 | * @name props 247 | * @prop {ual} ual 248 | */ 249 | UALBoxBase.propTypes = { 250 | ual: PropTypes.shape({ 251 | chains: PropTypes.arrayOf(PropTypes.object).isRequired, 252 | authenticators: PropTypes.arrayOf(PropTypes.object).isRequired, 253 | availableAuthenticators: PropTypes.arrayOf(PropTypes.object).isRequired, 254 | loading: PropTypes.bool.isRequired, 255 | activeUser: PropTypes.object, 256 | message: PropTypes.string.isRequired, 257 | broadcastStatus: PropTypes.func.isRequired, 258 | hideModal: PropTypes.func.isRequired, 259 | modal: PropTypes.bool.isRequired, 260 | appName: PropTypes.string.isRequired, 261 | logout: PropTypes.func.isRequired, 262 | restart: PropTypes.func.isRequired, 263 | error: PropTypes.object, 264 | authenticateWithoutAccountInput: PropTypes.func.isRequired, 265 | submitAccountForLogin: PropTypes.func.isRequired, 266 | }).isRequired, 267 | } 268 | -------------------------------------------------------------------------------- /src/components/modal/UALBoxParts.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import i18n from '../../i18n' 3 | 4 | import { UALAccountInput } from '../authentication/UALAccountInput' 5 | import { UALAuthButton } from '../authentication/UALAuthButton' 6 | import { UALLearnMore } from '../info/UALLearnMore' 7 | import { UALExitButton } from '../misc/UALExitButton' 8 | import { UALErrorMessage } from '../info/UALErrorMessage' 9 | import { UALLoadingIcon } from '../misc/UALLoadingIcon' 10 | import { UALInstallAuth } from '../authentication/UALInstallAuth' 11 | 12 | import { title, titleSecondary } from '../../styles/title' 13 | import { backButton, backButtonWrapper, backButtonText } from '../../styles/buttons/back' 14 | import { retryButton } from '../../styles/buttons/retry' 15 | import { secondaryInstructionsText, secondaryInstructionsLight } from '../../styles/instructions' 16 | import { darkenColor } from '../../utils' 17 | 18 | /** 19 | * @class 20 | * @name UALBoxParts 21 | * @desc class for rendering sections of the UALBox Component 22 | */ 23 | export class UALBoxParts { 24 | static displayName = 'UALBoxParts' 25 | 26 | /** 27 | * returns an instance of UALAuthButton 28 | * 29 | * @memberof UALBoxParts 30 | * @method 31 | * @name authButton 32 | * @return {ReactElement} 33 | */ 34 | static authButton(authenticator, index, instructions, authenticate, checkAuthenticators, enterInstallScreen) { 35 | return ( 36 | 45 | ) 46 | } 47 | 48 | /** 49 | * returns an instance of UALAuthInput 50 | * 51 | * @memberof UALBoxParts 52 | * @method 53 | * @name accountLogin 54 | * @return {ReactElement} 55 | */ 56 | static accountLogin(submitAccountForLogin, loading, authenticator) { 57 | return ( 58 | 63 | ) 64 | } 65 | 66 | /** 67 | * returns the secondary instructions for a given screen 68 | * 69 | * @memberof UALBoxParts 70 | * @method 71 | * @name secondaryInstructions 72 | * @return {HTML} 73 | */ 74 | static secondaryInstructions({ secondaryInstructions, showInstallScreen }, refreshBox) { 75 | const retry = ( 76 | 83 | {i18n.t('retry')} 84 | 85 | ) 86 | if (secondaryInstructions !== '') { 87 | const trueStyle = showInstallScreen ? secondaryInstructionsLight : secondaryInstructionsText 88 | return ( 89 |

90 | {secondaryInstructions} 91 | {' '} 92 | {!showInstallScreen && retry} 93 | {!showInstallScreen && '.'} 94 |

95 | ) 96 | } 97 | return null 98 | } 99 | 100 | /** 101 | * returns an instance of UALInstallAuth 102 | * 103 | * @memberof UALBoxParts 104 | * @method 105 | * @name installAuthenticatorSection 106 | * @return {ReactElement} 107 | */ 108 | static installAuthenticatorSection(authenticator) { 109 | return 110 | } 111 | 112 | /** 113 | * returns a back-button DOM element 114 | * 115 | * @memberof UALBoxParts 116 | * @method 117 | * @name backButton 118 | * @return {HTML} 119 | */ 120 | static backButton({ showAccountInput, error, loading, logout, showInstallScreen }, goBackToAuthSelect) { 121 | const goBackAction = error ? logout : goBackToAuthSelect 122 | if ((error || showAccountInput || showInstallScreen) && !loading) { 123 | return ( 124 |

125 | 126 | {i18n.t('goBack')} 127 | 128 |

129 | ) 130 | } 131 | return null 132 | } 133 | 134 | /** 135 | * returns instructions on dealing with no available authenticators 136 | * 137 | * @memberof UALBoxParts 138 | * @method 139 | * @name noAvailableAuthenticators 140 | * @return {HTML} 141 | */ 142 | static noAvailableAuthenticators() { 143 | return ( 144 |

147 | {i18n.t('noAuthenticatorsAvailableForDevice')} 148 |

149 | ) 150 | } 151 | 152 | /** 153 | * returns the main content of UALBox 154 | * 155 | * @memberof UALBoxParts 156 | * @method 157 | * @name mainContent 158 | * @return {ReactElement[]} 159 | */ 160 | static mainContent(app, authenticate, checkAuthenticators, enterInstallScreen) { 161 | const { 162 | submitAccountForLogin, 163 | authenticator, 164 | loading, 165 | error, 166 | showAccountInput, 167 | showInstallScreen, 168 | availableAuthenticators, 169 | instructions, 170 | } = app 171 | const authProps = [ 172 | authenticate, 173 | checkAuthenticators, 174 | enterInstallScreen, 175 | ] 176 | let mainContent = !loading 177 | ? availableAuthenticators.map((auth, index) => this.authButton(auth, index, instructions, ...authProps)) 178 | : 179 | if (!loading && !availableAuthenticators.length) { 180 | mainContent = this.noAvailableAuthenticators() 181 | } 182 | if (showAccountInput || showInstallScreen) { 183 | mainContent = showInstallScreen 184 | ? this.installAuthenticatorSection(authenticator) 185 | : this.accountLogin(submitAccountForLogin, loading, authenticator) 186 | } 187 | return !error && mainContent 188 | } 189 | 190 | /** 191 | * returns an instance of UALErrorMessage 192 | * 193 | * @memberof UALBoxParts 194 | * @method 195 | * @name errorMessage 196 | * @return {ReactElement} 197 | */ 198 | static errorMessage({ error }) { 199 | if (error) { 200 | return 201 | } 202 | return null 203 | } 204 | 205 | /** 206 | * returns an instance of UALLearnMore 207 | * 208 | * @memberof UALBoxParts 209 | * @method 210 | * @name learnMore 211 | * @return {ReactElement} 212 | */ 213 | static learnMore({ showAccountInput, showInstallScreen, error, loading }) { 214 | if (!showAccountInput && !error && !loading && !showInstallScreen) { 215 | return 216 | } 217 | return null 218 | } 219 | 220 | /** 221 | * returns the background color for a given screen 222 | * 223 | * @memberof UALBoxParts 224 | * @method 225 | * @name boxBackground 226 | * @return {Object} 227 | */ 228 | static boxBackground({ error, loading, showAccountInput, showInstallScreen, activeAuthenticator, authenticator }) { 229 | let authenticatorStyle = activeAuthenticator && activeAuthenticator.getStyle().background 230 | let background = {} 231 | if (!authenticatorStyle) { 232 | authenticatorStyle = authenticator && authenticator.getStyle().background 233 | } 234 | if (showAccountInput || loading || showInstallScreen || error) { 235 | background = { 236 | backgroundColor: darkenColor(authenticatorStyle) 237 | } 238 | } 239 | return background 240 | } 241 | 242 | /** 243 | * returns the main title of a screen of UALBox 244 | * 245 | * @memberof UALBoxParts 246 | * @method 247 | * @name boxTitle 248 | * @return {HTML} 249 | */ 250 | static boxTitle({ error, instructions, showAccountInput, showInstallScreen, loading, message }) { 251 | const boxTitle = error ? i18n.t('errorDuring', { src: error.source, type: error.type.toLowerCase() }) : instructions 252 | const titleStyle = error || showAccountInput || loading || showInstallScreen ? titleSecondary : title 253 | const titleContent = !loading || error ? boxTitle : message 254 | return

{titleContent}

255 | } 256 | 257 | /** 258 | * returns an instance of UALExitButton 259 | * 260 | * @memberof UALBoxParts 261 | * @method 262 | * @name exitButton 263 | * @return {ReactElement} 264 | */ 265 | static exitButton({ error, showAccountInput, showInstallScreen, loading, hideModal }) { 266 | return ( 267 | 271 | ) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/components/modal/UALContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import { 5 | container, 6 | containerAnimated, 7 | containerCenter, 8 | containerRight, 9 | containerLeft 10 | } from '../../styles/container' 11 | 12 | /** 13 | * @class 14 | * @name UALContainer 15 | * @desc wrapper for UALBox contents 16 | */ 17 | export const UALContainer = ({ enter, exit, transitionForward, children }) => { 18 | const end = transitionForward ? containerLeft : containerRight 19 | const start = transitionForward ? containerRight : containerLeft 20 | const startStyles = (enter || exit) ? containerAnimated : start 21 | const enterStyles = enter ? containerCenter : {} 22 | const exitStyles = exit ? end : {} 23 | return ( 24 |
25 | {children} 26 |
27 | ) 28 | } 29 | 30 | UALContainer.displayName = 'UALContainer' 31 | 32 | /** 33 | * @memberof UALContainer 34 | * @name props 35 | * @prop {boolean} enter - whether or not the screen has entered the user's view 36 | * @prop {boolean} exit - whether or not the screen has exited the user's view 37 | * @prop {boolean} transitionForward - whether or not user is progressing forward through screens 38 | * @prop {Node[]|Node} children - nested components 39 | */ 40 | UALContainer.propTypes = { 41 | enter: PropTypes.bool.isRequired, 42 | exit: PropTypes.bool.isRequired, 43 | transitionForward: PropTypes.bool.isRequired, 44 | children: PropTypes.oneOfType([ 45 | PropTypes.arrayOf(PropTypes.node), 46 | PropTypes.node, 47 | ]).isRequired, 48 | } 49 | -------------------------------------------------------------------------------- /src/components/provider/UALContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const UALContext = React.createContext() 4 | -------------------------------------------------------------------------------- /src/components/provider/UALProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import i18n from '../../i18n' 4 | import '../../types' 5 | import { UAL, UALError, UALErrorType } from 'universal-authenticator-library' 6 | 7 | import { UALContext } from './UALContext' 8 | import { DEFAULT_STATUS } from '../../constants/provider' 9 | import { UALBox } from '../modal/UALBox' 10 | 11 | import { modalStyles } from '../../styles/provider' 12 | import { baseFont } from '../../styles/base' 13 | 14 | /** 15 | * Wrapper component that provides a child app with access to UAL functionality 16 | */ 17 | export class UALProvider extends Component { 18 | static displayName = 'UALProvider' 19 | 20 | constructor(props) { 21 | super(props) 22 | /** 23 | * @namespace UAL 24 | */ 25 | this.state = { 26 | /** 27 | * @memberof UAL 28 | * @desc chain list from props 29 | * @type {Chain[]} chains 30 | */ 31 | chains: props.chains, 32 | /** 33 | * @memberof UAL 34 | * @desc authenticator instances from props 35 | * @type {Authenticator[]} authenticators 36 | */ 37 | authenticators: props.authenticators, 38 | /** 39 | * @memberof UAL 40 | * @desc available authenticator list 41 | * @type {Authenticator[]} availableAuthenticators 42 | */ 43 | availableAuthenticators: [], 44 | /** 45 | * @memberof UAL 46 | * @desc name of app 47 | * @type {string} appName 48 | */ 49 | appName: props.appName, 50 | /** 51 | * @memberof UAL 52 | * @desc whether or not show modal, initialized via props 53 | * @type {boolean} modal 54 | */ 55 | modal: props.modal, 56 | /** 57 | * @memberof UAL 58 | * @desc loading state of UAL 59 | * @type {boolean} loading 60 | */ 61 | loading: false, 62 | /** 63 | * @memberof UAL 64 | * @desc list of authenticated users 65 | * @type {User[]} users 66 | */ 67 | users: [], 68 | /** 69 | * @memberof UAL 70 | * @desc authenticator currently used 71 | * @type {Authenticator} activeAuthenticator 72 | */ 73 | activeAuthenticator: null, 74 | /** 75 | * @memberof UAL 76 | * @desc logged in user 77 | * @type {User} activeUser 78 | */ 79 | activeUser: null, 80 | /** 81 | * @memberof UAL 82 | * @desc whether or not activeAuthenticator should auto login 83 | * @type {boolean} isAutoLogin 84 | */ 85 | isAutoLogin: false, 86 | /** 87 | * @memberof UAL 88 | * @desc captured error if any 89 | * @type {UALError|null} error 90 | */ 91 | error: null, 92 | /** 93 | * @memberof UAL 94 | * @desc message, if any, accompanying current UAL state 95 | * @type {string} message 96 | */ 97 | message: '', 98 | /** 99 | * @memberof UAL 100 | * @function 101 | * @name hideModal 102 | * @desc hides the modal 103 | * @return {Void} 104 | */ 105 | hideModal: () => this.setState({ modal: false, loading: true, message: i18n.t('loadingAuthenticators') }), 106 | /** 107 | * @memberof UAL 108 | * @function 109 | * @name showModal 110 | * @desc shows the modal 111 | * @return {Void} 112 | */ 113 | showModal: () => { 114 | this.setState({ modal: true }) 115 | }, 116 | /** 117 | * @memberof UAL 118 | * @function 119 | * @name logout 120 | * @desc logs user out of authenticator and resets UAL state 121 | * @return {Void} 122 | */ 123 | logout: () => { 124 | const { activeAuthenticator } = this.state 125 | this.setState(DEFAULT_STATUS, () => activeAuthenticator && this.fullLogout(activeAuthenticator)) 126 | }, 127 | /** 128 | * @memberof UAL 129 | * @function 130 | * @name restart 131 | * @desc resets all available authenticators and resets UAL state 132 | * @return {Void} 133 | */ 134 | restart: () => { 135 | this.setState({ DEFAULT_STATUS }, () => { 136 | const { availableAuthenticators } = this.state 137 | availableAuthenticators.forEach(auth => auth.reset()) 138 | this.setState(availableAuthenticators) 139 | }) 140 | }, 141 | /** 142 | * @memberof UAL 143 | * @function 144 | * @name broadcastStatus 145 | * @desc dispatches a provider state update 146 | * @return {Void} 147 | */ 148 | broadcastStatus: (status = DEFAULT_STATUS) => this.setState(status), 149 | /** 150 | * @memberof UAL 151 | * @function 152 | * @name authenticateWithoutAccountInput 153 | * @desc attempts authentication with an authenticator not requiring account input 154 | * @return {Void} 155 | * @param {Authenticator} authenticator 156 | * @param {boolean} [false] isAutoLogin 157 | */ 158 | authenticateWithoutAccountInput: async (authenticator, isAutoLogin = false) => { 159 | const { broadcastStatus } = this.state 160 | broadcastStatus({ 161 | loading: true, 162 | message: i18n.t('continueWithAuthenticator', { authenticatorName: authenticator.getStyle().text }), 163 | activeAuthenticator: authenticator, 164 | }) 165 | try { 166 | const users = await authenticator.login() 167 | const accountName = await users[0].getAccountName() 168 | if (!isAutoLogin) { 169 | window.localStorage.setItem('UALLoggedInAuthType', authenticator.getName()) 170 | this.setUALInvalidateAt(authenticator) 171 | } 172 | broadcastStatus({ 173 | activeUser: users[users.length - 1], 174 | users, 175 | isAutoLogin, 176 | message: i18n.t('currentlyLoggedInAs', { accountName }), 177 | }) 178 | } catch (err) { 179 | broadcastStatus({ 180 | loading: false, 181 | error: err, 182 | message: err.message, 183 | }) 184 | } 185 | }, 186 | /** 187 | * @memberof UAL 188 | * @function 189 | * @name submitAccountForLogin 190 | * @desc attempts authentication 191 | * @return {Void} 192 | * @param {string} accountInput 193 | * @param {Authenticator} authenticator 194 | */ 195 | submitAccountForLogin: async (accountInput, authenticator) => { 196 | const { broadcastStatus } = this.state 197 | broadcastStatus({ 198 | loading: true, 199 | message: authenticator.requiresGetKeyConfirmation() 200 | ? i18n.t('waitWhileFindAccountWithConfirmation') 201 | : i18n.t('waitWhileFindAccount'), 202 | }) 203 | try { 204 | const users = await authenticator.login(accountInput) 205 | window.localStorage.setItem('UALLoggedInAuthType', authenticator.getName()) 206 | window.localStorage.setItem('UALAccountName', accountInput) 207 | broadcastStatus({ 208 | activeUser: users[users.length - 1], 209 | activeAuthenticator: authenticator, 210 | users, 211 | message: i18n.t('currentlyLoggedInAs', { accountName: accountInput }), 212 | }) 213 | this.setUALInvalidateAt(authenticator) 214 | } catch (err) { 215 | broadcastStatus({ 216 | error: err, 217 | message: err.message, 218 | loading: false, 219 | }) 220 | } 221 | }, 222 | } 223 | } 224 | 225 | componentDidMount() { 226 | const { chains, appName, authenticators, authenticateWithoutAccountInput, submitAccountForLogin } = this.state 227 | let type = window.localStorage.getItem('UALLoggedInAuthType') 228 | const invalidate = window.localStorage.getItem('UALInvalidateAt') 229 | const accountName = window.localStorage.getItem('UALAccountName') 230 | type = this.checkForInvalidatedSession(type, invalidate) 231 | const ual = new UAL(chains, appName, authenticators) 232 | const { availableAuthenticators, autoLoginAuthenticator } = ual.getAuthenticators() 233 | try { 234 | if (type) { 235 | const authenticator = this.getAuthenticatorInstance(type, availableAuthenticators) 236 | if (!authenticator) { 237 | throw new Error('authenticator instance not found') 238 | } 239 | const availableCheck = setInterval(() => { 240 | if (!authenticator.isLoading()) { 241 | clearInterval(availableCheck) 242 | // Only Ledger requires an account name 243 | if (accountName) { 244 | submitAccountForLogin(accountName, authenticator) 245 | } else { 246 | authenticateWithoutAccountInput(authenticator) 247 | } 248 | } 249 | }, 250) 250 | } 251 | } catch (e) { 252 | this.clearCache() 253 | const msg = i18n.t('sessionEndedNeedLogin') 254 | const source = type || 'Universal Authenticator Library' 255 | const errType = UALErrorType.Login 256 | console.warn(new UALError(msg, errType, e, source)) 257 | } finally { 258 | this.fetchAuthenticators(availableAuthenticators, autoLoginAuthenticator) 259 | } 260 | } 261 | 262 | componentDidUpdate() { 263 | const { loading, message, availableAuthenticators, broadcastStatus } = this.state 264 | if (loading && message === i18n.t('loadingAuthenticators') && availableAuthenticators.length) { 265 | broadcastStatus({ message: i18n.t('authenticatorsLoaded'), loading: false }) 266 | } 267 | } 268 | 269 | /** 270 | * Verifies a logged in user's authenticator is still app supported 271 | * 272 | * @method 273 | * @param {string} type - authenticator type of sessioned user 274 | * @param {Object[]} availableAuthenticators - list of available app supported authenticators 275 | * @return {number|boolean} 276 | */ 277 | getAuthenticatorInstance = (type, availableAuthenticators) => { 278 | const loggedIn = availableAuthenticators.filter(auth => auth.getName() === type) 279 | if (!loggedIn.length) { 280 | this.clearCache() 281 | } 282 | return loggedIn.length ? loggedIn[0] : false 283 | } 284 | 285 | /** 286 | * Checks if the saved browser session has passed the UALInvalidateAt date and clear the cache if true 287 | * 288 | * @method 289 | * @param {string} type - UALLoggedInAuthType value 290 | * @param {string} invalidate - UALInvalidateAt value in string formatted date 291 | * @return {string|undefined} - UALLoggedInAuthType value or undefined if no longer valid 292 | */ 293 | checkForInvalidatedSession = (type, invalidate) => { 294 | if (type && invalidate && new Date(invalidate) <= new Date()) { 295 | this.clearCache() 296 | return undefined 297 | } 298 | return type 299 | } 300 | 301 | /** 302 | * Sets UALInvalidateAt value to local storage depending on amount of seconds set in authenticator 303 | * 304 | * @method 305 | * @param {Authenticator} authenticator - should-invalidate-after authenticator 306 | * @return {Void} 307 | */ 308 | setUALInvalidateAt = (authenticator) => { 309 | const invalidateSeconds = authenticator.shouldInvalidateAfter() 310 | const invalidateAt = new Date() 311 | invalidateAt.setSeconds(invalidateAt.getSeconds() + invalidateSeconds) 312 | window.localStorage.setItem('UALInvalidateAt', invalidateAt) 313 | } 314 | 315 | /** 316 | * Renders available authenticators or starts auto-login process if applicable 317 | * 318 | * @method 319 | * @param {Authenticator[]} availableAuthenticators - list of available app supported authenticators 320 | * @param {Authenticator} autoLoginAuthenticator - auto-login authenticator 321 | * @return {Void} 322 | */ 323 | fetchAuthenticators = (availableAuthenticators, autoLoginAuthenticator) => { 324 | const { authenticateWithoutAccountInput } = this.state 325 | if (autoLoginAuthenticator) { 326 | this.setState({ availableAuthenticators: [autoLoginAuthenticator] }, () => { 327 | const availableCheck = setInterval(() => { 328 | if (!autoLoginAuthenticator.isLoading()) { 329 | clearInterval(availableCheck) 330 | authenticateWithoutAccountInput(autoLoginAuthenticator, true) 331 | } 332 | }, 250) 333 | }) 334 | } else { 335 | this.setState({ availableAuthenticators }, () => { 336 | this.setState({ message: i18n.t('authenticatorsLoaded') }) 337 | }) 338 | } 339 | } 340 | 341 | /** 342 | * Clears locally stored user session 343 | * 344 | * @method 345 | * @return {Void} 346 | */ 347 | clearCache = () => { 348 | window.localStorage.removeItem('UALLoggedInAuthType') 349 | window.localStorage.removeItem('UALAccountName') 350 | window.localStorage.removeItem('UALInvalidateAt') 351 | } 352 | 353 | /** 354 | * Clears localStorage and logs out user 355 | * 356 | * @method 357 | * @param {Authenticator} authenticator - used authenticator 358 | * @return {Void} 359 | */ 360 | fullLogout = (authenticator) => { 361 | this.clearCache() 362 | authenticator.logout() 363 | .catch(e => console.warn(e)) 364 | } 365 | 366 | render() { 367 | const modal = this.state.modal &&
368 | return ( 369 | 370 | 371 | { modal } 372 | { this.props.children } 373 | 374 | ) 375 | } 376 | } 377 | 378 | /** 379 | * @memberof UALProvider 380 | * @name props 381 | * @prop {Chain[]} chains - list of chains the app supports 382 | * @prop {Authenticator[]} authenticators - list of authenticators the app supports 383 | * @prop {Node[]|Node} children - child app(s) 384 | * @prop {string} appName - name of app 385 | * @prop {boolean} modal - whether or not to show modal 386 | */ 387 | UALProvider.propTypes = { 388 | 389 | chains: PropTypes.arrayOf(PropTypes.object).isRequired, 390 | authenticators: PropTypes.arrayOf(PropTypes.object).isRequired, 391 | children: PropTypes.oneOfType([ 392 | PropTypes.arrayOf(PropTypes.node), 393 | PropTypes.node, 394 | ]).isRequired, 395 | appName: PropTypes.string.isRequired, 396 | modal: PropTypes.bool, 397 | } 398 | 399 | UALProvider.defaultProps = { 400 | modal: false, 401 | } 402 | -------------------------------------------------------------------------------- /src/components/provider/withUAL.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { UALContext } from './UALContext' 4 | 5 | /** 6 | * @type {function} 7 | * @name withUAL 8 | * @desc Function for making a component a consumer of the UAL context 9 | */ 10 | export const withUAL = WrappedComponent => props => { 11 | const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component' 12 | const WithUAL = wrappedProps => ( 13 | 14 | { context => } 15 | 16 | ) 17 | WithUAL.displayName = `withUAL(${displayName})` 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /src/constants/authentication.js: -------------------------------------------------------------------------------- 1 | export const buttonState = { 2 | LOADING: 'loading', 3 | ERRORED: 'errored', 4 | AVAILABLE: 'available', 5 | UNAVAILABLE: 'unavailable', 6 | } 7 | 8 | export const errorColors = { 9 | DARK_GREY: '#9096A8', 10 | LIGHT_GREY: '#D5D8E2', 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/box.js: -------------------------------------------------------------------------------- 1 | import i18n from '../i18n' 2 | 3 | export const boxTitles = { 4 | NORMAL: i18n.t('instructionsToContinue'), 5 | ERROR: i18n.t('noAvailableAuthenticatorsTitle'), 6 | } 7 | 8 | export const installGuide = i18n.t('noAvailableAuthenticatorsContent') 9 | 10 | export const defaultBoxState = { 11 | containerEnter: false, 12 | containerExit: false, 13 | transitionForward: true, 14 | showAccountInput: false, 15 | showInstallScreen: false, 16 | authenticator: null, 17 | instructions: boxTitles.NORMAL, 18 | secondaryInstructions: '', 19 | } 20 | -------------------------------------------------------------------------------- /src/constants/provider.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_STATUS = { 2 | loading: false, 3 | activeUser: null, 4 | activeAuthenticator: null, 5 | users: [], 6 | error: null, 7 | message: '', 8 | } 9 | -------------------------------------------------------------------------------- /src/i18n/en-US.js: -------------------------------------------------------------------------------- 1 | import th from './translationHelpers' 2 | 3 | export default { 4 | logout: th('Logout'), 5 | loggedInAs: th('Logged in as {{accountName}}'), 6 | instructionsToContinue: th('To continue please select an option'), 7 | loadingAuthenticators: th('Loading Authenticators...'), 8 | enterUsername: th('Next, please enter your username'), 9 | learnMore: th('Learn more'), 10 | learnMoreText: th('This option allows you to connect to your favorite key manager app.'), 11 | learnMoreAccept: th('I got it!'), 12 | selectALoginService: th('Please select a service to log in'), 13 | accountName: th('Account Name'), 14 | continue: th('Continue'), 15 | noAvailableAuthenticatorsTitle: th('Pardon the interruption'), 16 | noAvailableAuthenticatorsContent: th(`It looks like you have no available authenticators. Select the 17 | authenticator that you wish to download and install. If you see an authenticator that you 18 | already have, make sure the corresponding application is running and`), 19 | getStarted: th('To get started with {{authName}}, install the app at the link below.'), 20 | leaveAndInstall: th('Leave and Install'), 21 | welcomeAccount: th('Welcome to {{authName}}'), 22 | retry: th('retry'), 23 | noAuthenticatorsAvailableForDevice: th('No authenticators are available for your current browser and/or device.'), 24 | continueWithAuthenticator: th('Continue with {{authenticatorName}}'), 25 | currentlyLoggedInAs: th('Currently, logged in as {{accountName}}'), 26 | waitWhileFindAccount: th('Please wait while we find your account.'), 27 | waitWhileFindAccountWithConfirmation: th('Please approve the request from your device.'), 28 | authenticatorsLoaded: th('Authenticators loaded.'), 29 | sessionEndedNeedLogin: th('User session has ended. Login required.'), 30 | goBack: th('Go Back'), 31 | errorDuring: th('{{src}} encountered an error during {{type}}'), 32 | } 33 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | 3 | import resources from './resources' 4 | 5 | // this module isn't quite compliant, so it's gotta be imported like this to work 6 | const LanguageDetector = require('i18next-browser-languagedetector') 7 | 8 | i18n 9 | .use(LanguageDetector) 10 | .init({ 11 | resources, 12 | lng: 'en-US', 13 | fallbackLng: 'en', 14 | ns: ['ualcore'], // namespaces to load 15 | defaultNS: 'ualcore', // defaults to 'translation' 16 | 17 | interpolation: { 18 | escapeValue: false, 19 | }, 20 | 21 | react: { 22 | wait: true, 23 | bindI18n: 'languageChanged loaded', 24 | bindStore: 'added removed', 25 | nsMode: 'default', 26 | }, 27 | }) 28 | .catch(e => console.warn(e)) 29 | 30 | export default i18n 31 | -------------------------------------------------------------------------------- /src/i18n/resources.js: -------------------------------------------------------------------------------- 1 | import enUS from './en-US' 2 | 3 | export default { 4 | en: { // this is the default namespace; this can also be a "namespace" like 'US' as in 'en-US' 5 | ualcore: enUS, 6 | }, 7 | 'en-US': { // this is the default namespace; this can also be a "namespace" like 'US' as in 'en-US' 8 | ualcore: enUS, 9 | }, 10 | es: { 11 | ualcore: { 12 | logout: 'Cerra sesión', 13 | instructionsToContinue: 'para continuar por favor hace una eleción', 14 | }, 15 | }, 16 | 'pt-BR': { 17 | ualcore: { 18 | logout: 'Sair', 19 | instructionsToContinue: 'para continuar por favor hace una eleción', 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/i18n/translationHelpers.js: -------------------------------------------------------------------------------- 1 | const translationTestHelper = (stringTemplate) => { 2 | const prefixForTesting = '' 3 | return `${prefixForTesting}${stringTemplate}` 4 | } 5 | 6 | const th = translationTestHelper 7 | 8 | export default th 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { UALBox } from './components/modal/UALBox' 2 | export { UALContext } from './components/provider/UALContext' 3 | export { withUAL } from './components/provider/withUAL' 4 | export { UALProvider } from './components/provider/UALProvider' 5 | -------------------------------------------------------------------------------- /src/styles/authenticator.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const authButton = { 4 | fontWeight: '100', 5 | color: '#ffffff', 6 | borderRadius: '6px', 7 | width: '100%', 8 | margin: '20px auto', 9 | boxShadow: '0px 0px 5px rgba(0,0,0,0.1)', 10 | opacity: '1', 11 | transition: '0.3s', 12 | WebkitTransition: '0.3s', 13 | outline: 'none', 14 | } 15 | 16 | export const buttonHover = { 17 | cursor: 'pointer', 18 | transform: 'scale(1.03)', 19 | } 20 | 21 | export const authIcon = { 22 | maxHeight: '30px', 23 | maxWidth: '100%', 24 | margin: 'auto', 25 | marginTop: '7%', 26 | display: 'block', 27 | } 28 | 29 | export const authIconWrapper = { 30 | ...base, 31 | display: 'inline-block', 32 | float: 'left', 33 | width: '50px', 34 | padding: '8.5px 10px', 35 | backgroundColor: 'rgba(0,0,0,0.15)', 36 | borderRadius: '5px 0px 0px 5px', 37 | } 38 | 39 | export const authText = { 40 | ...base, 41 | display: 'inline-block', 42 | padding: '12px 13px 12px 15px', 43 | width: 'calc(100% - 50px)', 44 | } 45 | 46 | export const authTextFont = { 47 | fontSize: '20px', 48 | fontWeight: 'bold', 49 | letterSpacing: '1.1px', 50 | } 51 | 52 | export const chevron = { 53 | float: 'right', 54 | height: '16px', 55 | marginTop: '2px', 56 | } 57 | 58 | export const errored = { 59 | color: 'rgb(144, 150, 168)', 60 | fontSize: '1.5rem', 61 | } 62 | -------------------------------------------------------------------------------- /src/styles/base.js: -------------------------------------------------------------------------------- 1 | export const base = { 2 | boxSizing: 'border-box', 3 | fontFamily: '"Source Sans Pro", sans-serif', 4 | } 5 | 6 | export const baseLink = { 7 | textDecoration: 'none', 8 | color: 'inherit', 9 | } 10 | 11 | export const baseFont = '@import https://fonts.googleapis.com/css?family=Source+Sans+Pro;' 12 | -------------------------------------------------------------------------------- /src/styles/box.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const box = { 4 | ...base, 5 | backgroundColor: 'white', 6 | position: 'static', 7 | overflow: 'hidden', 8 | padding: '10px 30px', 9 | width: '420px', 10 | height: 'auto', 11 | maxWidth: '100%', 12 | maxHeight: '100%', 13 | margin: '50px auto', 14 | marginTop: '10vh', 15 | boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', 16 | borderRadius: '4px', 17 | } 18 | -------------------------------------------------------------------------------- /src/styles/buttons/back.js: -------------------------------------------------------------------------------- 1 | export const backButtonWrapper = { 2 | textAlign: 'center', 3 | } 4 | 5 | export const backButton = { 6 | fontSize: '0.8rem', 7 | cursor: 'pointer', 8 | outline: 'none', 9 | } 10 | 11 | export const backButtonText = { 12 | color: 'white', 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/buttons/exit.js: -------------------------------------------------------------------------------- 1 | export const exitWrapper = { 2 | margin: '5px -5px 20px 0px', 3 | } 4 | 5 | export const exit = { 6 | float: 'right', 7 | transform: 'scale(1.2)', 8 | marginRight: '-8px', 9 | fontWeight: '100', 10 | color: 'rgb(72, 151, 248)', 11 | transition: '.3s', 12 | cursor: 'pointer', 13 | } 14 | 15 | export const exitHover = { 16 | transform: 'scale(1.3)', 17 | } 18 | -------------------------------------------------------------------------------- /src/styles/buttons/retry.js: -------------------------------------------------------------------------------- 1 | export const retryButton = { 2 | color: 'rgb(72, 151, 248)', 3 | cursor: 'pointer', 4 | outline: 'none', 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/container.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const container = { 4 | ...base, 5 | minHeight: '20px', 6 | width: '100%', 7 | height: '100%', 8 | padding: '15px', 9 | } 10 | 11 | export const containerAnimated = { 12 | transition: '0.3s', 13 | WebkitTransition: '0.3s', 14 | } 15 | 16 | export const containerCenter = { 17 | marginLeft: '0%', 18 | } 19 | 20 | export const containerLeft = { 21 | marginLeft: '-120%', 22 | } 23 | 24 | export const containerRight = { 25 | marginLeft: '120%', 26 | } 27 | -------------------------------------------------------------------------------- /src/styles/error.js: -------------------------------------------------------------------------------- 1 | export const errorMessage = { 2 | color: 'white', 3 | fontWeight: '100', 4 | marginBottom: '1rem', 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/info.js: -------------------------------------------------------------------------------- 1 | export const learnMore = { 2 | transition: 'max-height ease 0.2s, transform ease-out 0.3s, opacity ease-in 0.7s', 3 | WebkitTransition: 'max-height ease 0.2s, transform ease-out 0.3s, opacity ease-in 0.7s', 4 | overflow: 'hidden', 5 | transform: 'scale(0.5)', 6 | transformOrigin: 'top left', 7 | opacity: '0', 8 | maxHeight: '0', 9 | margin: '0', 10 | } 11 | 12 | export const infoExpanded = { 13 | opacity: '1', 14 | transform: 'scale(1)', 15 | maxHeight: '500px', 16 | } 17 | 18 | export const learnMoreText = { 19 | color: 'rgba(49, 71, 128, 0.7)', 20 | fontSize: '0.8rem', 21 | fontWeight: '100', 22 | margin: '0px', 23 | } 24 | 25 | export const learnMoreButton = { 26 | marginTop: '0px', 27 | color: 'rgb(72, 151, 248)', 28 | fontSize: '0.9rem', 29 | fontWeight: '100', 30 | transition: '0.3s', 31 | WebkitTransition: '0.3s', 32 | cursor: 'pointer', 33 | outline: 'none', 34 | } 35 | 36 | export const learnMoreIcon = { 37 | fontSize: '1rem', 38 | verticalAlign: 'middle', 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/input.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | import { authButton } from './authenticator' 3 | 4 | export const inputWrapper = { 5 | ...base, 6 | marginTop: '10px', 7 | } 8 | 9 | export const inputStyle = { 10 | display: 'block', 11 | width: '100%', 12 | padding: '10px', 13 | fontSize: '1.3em', 14 | boxSizing: 'border-box', 15 | margin: '0px auto 15px auto', 16 | maxWidth: '400px', 17 | transition: 'opacity 0.3s', 18 | WebkitTransition: 'opacity 0.3s', 19 | border: 'none', 20 | color: 'white', 21 | borderBottom: '1px solid white', 22 | outline: 'none', 23 | backgroundColor: 'rgba(0,0,0,0)', 24 | } 25 | 26 | export const buttonDisabled = { 27 | ...authButton, 28 | opacity: '0.5', 29 | } 30 | 31 | export const buttonEnabled = { 32 | ...authButton, 33 | opacity: '1', 34 | } 35 | -------------------------------------------------------------------------------- /src/styles/installation.js: -------------------------------------------------------------------------------- 1 | import { authButton } from './authenticator' 2 | 3 | export const installButton = { 4 | ...authButton, 5 | cursor: 'pointer', 6 | } 7 | 8 | export const installButtonWrapper = { 9 | paddingTop: '20px', 10 | paddingBottom: '30px', 11 | } 12 | 13 | export const buttonText = { 14 | padding: '15px', 15 | textAlign: 'center', 16 | } 17 | -------------------------------------------------------------------------------- /src/styles/instructions.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const secondaryInstructionsText = { 4 | ...base, 5 | fontSize: '1rem', 6 | marginTop: '0.5rem', 7 | marginBottom: '2rem', 8 | color: '#607C9F', 9 | } 10 | 11 | export const secondaryInstructionsLight = { 12 | ...secondaryInstructionsText, 13 | color: 'white', 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/loader.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const loadingIcon = { 4 | ...base, 5 | width: '41px', 6 | textAlign: 'center', 7 | marginTop: '-4px', 8 | } 9 | 10 | export const loadingIconWithContainer = { 11 | ...loadingIcon, 12 | width: '100%', 13 | padding: '20px', 14 | } 15 | 16 | export const loadingElementThree = { 17 | width: '9px', 18 | height: '9px', 19 | margin: '1px', 20 | backgroundColor: 'white', 21 | borderRadius: '100%', 22 | display: 'inline-block', 23 | WebkitAnimation: 'ual-bouncedelay 1.4s infinite ease-in-out both', 24 | animation: 'ual-bouncedelay 1.4s infinite ease-in-out both', 25 | } 26 | 27 | export const loadingElementOne = { 28 | ...loadingElementThree, 29 | WebkitAnimationDelay: '-0.32s', 30 | AnimationDelay: '-0.32s', 31 | } 32 | 33 | export const loadingElementTwo = { 34 | ...loadingElementThree, 35 | WebkitAnimationDelay: '-0.16s', 36 | AnimationDelay: '-0.16s', 37 | } 38 | 39 | export const loadingElementCSS = ` 40 | @-webkit-keyframes ual-bouncedelay { 41 | 0%, 80%, 100% { -webkit-transform: scale(0) } 42 | 40% { -webkit-transform: scale(1.0) } 43 | } 44 | 45 | @keyframes ual-bouncedelay { 46 | 0%, 80%, 100% { 47 | -webkit-transform: scale(0); 48 | transform: scale(0); 49 | } 40% { 50 | -webkit-transform: scale(1.0); 51 | transform: scale(1.0); 52 | } 53 | } 54 | ` 55 | -------------------------------------------------------------------------------- /src/styles/mediaQuery.js: -------------------------------------------------------------------------------- 1 | export const mediaQuery = ` 2 | @media only screen and (max-width: 480px) { 3 | #ual-box { 4 | width: 100% !important; 5 | height: 100% !important; 6 | box-shadow: none !important; 7 | transform: none !important; 8 | margin: 0 !important; 9 | border-radius: 0px !important; 10 | } 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /src/styles/provider.js: -------------------------------------------------------------------------------- 1 | import { base } from './base' 2 | 3 | export const modalStyles = { 4 | ...base, 5 | position: 'fixed', 6 | zIndex: '999999999999', 7 | left: '0', 8 | top: '0', 9 | width: '100%', 10 | height: '100%', 11 | margin: 'auto', 12 | overflow: 'auto', 13 | backgroundColor: 'rgb(49, 71, 128, 0.8)', 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/title.js: -------------------------------------------------------------------------------- 1 | export const titleWrapper = { 2 | padding: '15px 15px 0px 15px', 3 | } 4 | 5 | const titleBase = { 6 | margin: '0', 7 | textAlign: 'left', 8 | } 9 | 10 | export const title = { 11 | ...titleBase, 12 | color: 'rgb(49, 71, 128)', 13 | } 14 | 15 | export const titleSecondary = { 16 | ...titleBase, 17 | color: 'white', 18 | } 19 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines a supported chain 3 | * 4 | * @typedef Chain 5 | * @type {object} 6 | * @property {string} chainId - The chainId for the chain. 7 | * @property {RpcEndpoint[]} rpcEndpoints - One or more rpcEndpoints associated with that chainId. 8 | */ 9 | 10 | /** 11 | * Defines an RpcEndpoint 12 | * 13 | * @typedef RpcEndpoint 14 | * @type {object} 15 | * @property {string} protocol 16 | * @property {string} host 17 | * @property {number} port 18 | * @property {string} path 19 | */ -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Code inspired by Chris Coyier's vanilla css lighten/darken color solution 3 | * https://css-tricks.com/snippets/javascript/lighten-darken-color/ 4 | */ 5 | 6 | const hexToRGB = (color) => { 7 | const num = parseInt(color.replace('#', ''), 16) 8 | const r = (num >> 16) - 30 // eslint-disable-line 9 | const b = ((num >> 8) & 0x00FF) - 30 // eslint-disable-line 10 | const g = (num & 0x0000FF) - 30 // eslint-disable-line 11 | return [r, b, g] 12 | } 13 | 14 | const limitValues = (value) => { 15 | if (value > 255) return 255 16 | if (value < 0) return 0 17 | return value 18 | } 19 | 20 | export const darkenColor = (color) => { 21 | let colors 22 | if (!color) { 23 | return '#1A3270' 24 | } 25 | if (color.indexOf('rgb') !== -1) { 26 | colors = color.replace('rgb(', '') 27 | .replace(')', '') 28 | .split(',') 29 | .map(num => parseInt(num, 10) - 30) 30 | .map(limitValues) 31 | } else { 32 | colors = hexToRGB(color).map(limitValues) 33 | } 34 | const [r, g, b] = colors 35 | return `rgb(${r}, ${g}, ${b})` 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "rootDir": "./src", 6 | "outDir": "dist", 7 | "strict": true, 8 | "noImplicitAny": false, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | "allowJs": true, 16 | "jsx": "react" 17 | }, 18 | "lib": ["esnext"], 19 | "include": [ 20 | "src/**/*", 21 | "tests/**/*" 22 | ], 23 | "exclude": [ 24 | "src/**/*.test.ts" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------