├── .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 | 
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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------