├── .gitignore
├── .node-version
├── LICENSE
├── README.md
├── netlify.toml
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── robots.txt
├── src
├── App.test.js
├── App.tsx
├── Onboard.ts
├── abi
│ └── AuthereumAccount.json
├── config.ts
├── index.css
├── index.tsx
├── react-app-env.d.ts
├── serviceWorker.js
└── setupTests.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | package-lock.json
27 | yarn.lock
28 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 11.15
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT license
2 |
3 | Copyright (C) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Authereum Direct
2 |
3 | > Interact with your Authereum contract-based account directly.
4 |
5 | ## Getting started
6 |
7 | ```bash
8 | git clone git@github.com:authereum/direct.git
9 | cd direct
10 | npm install
11 | npm run start
12 | ```
13 |
14 | Demo
15 |
16 | - [https://direct.authereum.com/](https://direct.authereum.com/)
17 | - [https://authereum-direct.netlify.app/](https://authereum-direct.netlify.app/)
18 | - [https://ipfs.infura.io/ipfs/QmVpdpRFwuzvC9PBcQfHgYtz6SQANsJ1qA1MuJcwsZMQoR](https://ipfs.infura.io/ipfs/QmVpdpRFwuzvC9PBcQfHgYtz6SQANsJ1qA1MuJcwsZMQoR)
19 |
20 | ## License
21 |
22 | [MIT](LICENSE)
23 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@authereum/abi": "0.0.2-beta.30",
7 | "@authereum/utils": "0.0.1-beta.234",
8 | "@material-ui/core": "4.11.0",
9 | "@material-ui/icons": "4.9.1",
10 | "@material-ui/lab": "4.0.0-alpha.56",
11 | "@testing-library/jest-dom": "4.2.4",
12 | "@testing-library/react": "9.5.0",
13 | "@testing-library/user-event": "7.2.1",
14 | "@types/jest": "26.0.14",
15 | "@types/node": "14.11.8",
16 | "@types/react": "16.9.52",
17 | "@types/react-dom": "16.9.8",
18 | "@walletconnect/browser": "1.0.0",
19 | "@walletconnect/client": "1.2.2",
20 | "bnc-onboard": "1.14.0",
21 | "ethers": "5.0.17",
22 | "events": "3.2.0",
23 | "react": "16.13.1",
24 | "react-dom": "16.13.1",
25 | "react-scripts": "3.4.3",
26 | "typescript": "4.0.3"
27 | },
28 | "scripts": {
29 | "dev": "BROWSER=none npm run start",
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject",
34 | "lint": "prettier-standard --format"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "devDependencies": {
52 | "prettier-standard": "16.4.1"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/authereum/direct/f17e6548840f0d979040f039cf4453edd8359c38/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 | Authereum Direct
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from '@testing-library/react'
3 | import App from './App'
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render()
7 | const linkElement = getByText(/learn react/i)
8 | expect(linkElement).toBeInTheDocument()
9 | })
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events'
2 | import React, { useState, useEffect } from 'react'
3 | import { ethers } from 'ethers'
4 | import validPrivateKey from '@authereum/utils/core/validPrivateKey'
5 | import WalletConnect from '@walletconnect/client'
6 | import encodeParameters from '@authereum/utils/core/encodeParameters'
7 | import getNetworkId from '@authereum/utils/core/getNetworkId'
8 | import hexToUtf8 from '@authereum/utils/core/hexToUtf8'
9 | import Onboard from './Onboard'
10 |
11 | import Button from '@material-ui/core/Button'
12 | import TextField from '@material-ui/core/TextField'
13 | import Grid from '@material-ui/core/Grid'
14 | import Card from '@material-ui/core/Card'
15 | import CardContent from '@material-ui/core/CardContent'
16 |
17 | function App () {
18 | const [ee] = React.useState(() => new EventEmitter())
19 | const [privateKey, setPrivateKey] = useState(() => {
20 | return localStorage.getItem('privateKey') || ''
21 | })
22 | const [contractAddress, setContractAddress] = useState(() => {
23 | return localStorage.getItem('contractAddress') || ''
24 | })
25 | const [wallet, setWallet] = useState()
26 | const [onboard] = useState(() => {
27 | const network = localStorage.getItem('networkName') || 'mainnet'
28 | const networkId = getNetworkId(network)
29 | const rpcUrl = `https://${network}.rpc.authereum.com`
30 | return new Onboard({ networkId, rpcUrl })
31 | })
32 | const [walletName, setWalletName] = useState(
33 | localStorage.getItem('walletName')
34 | )
35 | const [info, setInfo] = useState({})
36 | const [connectUri, setConnectUri] = useState('')
37 | const [walletConnectSession, setWalletConnectSession] = useState(
38 | localStorage.getItem('walletconnect')
39 | ? JSON.parse(localStorage.getItem('walletconnect') as string)
40 | : null
41 | )
42 | const [walletConnector, setWalletConnector] = useState()
43 | const [networkName, setNetworkName] = useState(() => {
44 | return localStorage.getItem('networkName') || 'mainnet'
45 | })
46 | const [, setRpcUrl] = useState(() => {
47 | const network = localStorage.getItem('networkName') || 'mainnet'
48 | return `https://${network}.rpc.authereum.com`
49 | })
50 | const [provider, setProvider] = useState(() => {
51 | const network = localStorage.getItem('networkName') || 'mainnet'
52 | const rpcUrl = `https://${network}.rpc.authereum.com`
53 | return new ethers.providers.JsonRpcProvider(rpcUrl)
54 | })
55 | const [callRequest, setCallRequest] = React.useState(null)
56 | const [callRequestEditable, setCallRequestEditable] = React.useState(
57 | false
58 | )
59 | const [customTx, setCustomTx] = React.useState(() => {
60 | return localStorage.getItem('customTx') || ''
61 | })
62 | const [customTxHash, setCustomTxHash] = React.useState('')
63 | const exampleTx = {
64 | to: '',
65 | value: '0x0',
66 | data: '0x0',
67 | gasLimit: '0x0',
68 | gasPrice: '0x0'
69 | }
70 |
71 | useEffect(() => {
72 | localStorage.setItem('networkName', networkName)
73 | const rpcUrl = `https://${networkName}.rpc.authereum.com`
74 | setRpcUrl(rpcUrl)
75 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl)
76 | setProvider(provider)
77 | const networkId = getNetworkId(networkName)
78 | onboard.setConfig({ networkId })
79 | }, [networkName, onboard])
80 |
81 | useEffect(() => {
82 | if (!privateKey) {
83 | return
84 | }
85 |
86 | if (validPrivateKey(privateKey)) {
87 | const priv = privateKey.replace(/^(0x)?/, '0x')
88 | const wal = new ethers.Wallet(priv, provider)
89 | localStorage.setItem('privateKey', priv)
90 | setWallet(wal)
91 | localStorage.setItem('walletName', 'privateKey')
92 | }
93 | }, [privateKey, provider])
94 |
95 | useEffect(() => {
96 | const setup = async () => {
97 | if (!walletName) return
98 | if (walletName === 'privateKey') return
99 | await onboard.walletCheck(walletName)
100 | const state = await onboard.getState()
101 | const { address } = state
102 | if (!address) {
103 | return
104 | }
105 |
106 | setInfo({ address })
107 | const signer = onboard.getSigner()
108 | setWallet(signer)
109 | }
110 |
111 | setup()
112 | }, [onboard, walletName])
113 |
114 | const handlePrivateKeyChange = (event: any) => {
115 | event.preventDefault()
116 | const value = event.target.value.trim()
117 | setPrivateKey(value)
118 | }
119 |
120 | const handleConnectUri = (event: any) => {
121 | event.preventDefault()
122 |
123 | const uri = event.target.value
124 | setConnectUri(uri)
125 | localStorage.setItem('uri', uri)
126 | }
127 |
128 | function getCachedSession () {
129 | const local = localStorage ? localStorage.getItem('walletconnect') : null
130 |
131 | let session = null
132 | if (local) {
133 | try {
134 | session = JSON.parse(local)
135 | } catch (error) {
136 | throw error
137 | }
138 | }
139 | return session
140 | }
141 |
142 | const handleConnect = (event: any) => {
143 | onboard.showWalletSector()
144 | }
145 |
146 | // walletconnect doesn't have a way to unsubscribe from event emitter,
147 | // so we use a custom event emitter as a workaround.
148 | useEffect(() => {
149 | const events = [
150 | 'connect',
151 | 'disconnect',
152 | 'session_request',
153 | 'call_request',
154 | 'error'
155 | ]
156 | for (let name of events) {
157 | walletConnector?.on(name, (...args: any[]) => ee.emit(name, ...args))
158 | }
159 | }, [walletConnector, ee])
160 |
161 | const handleApprove = (event: any) => {
162 | event.preventDefault()
163 |
164 | approveCallRequest(callRequest)
165 | }
166 |
167 | const handleReject = (event: any) => {
168 | event.preventDefault()
169 |
170 | rejectCallRequest(callRequest)
171 | }
172 |
173 | const rejectCallRequest = (payload: any) => {
174 | if (payload.error) {
175 | walletConnector.rejectRequest(payload)
176 | } else {
177 | walletConnector.rejectRequest({ id: payload.id, error: 'Cancelled' })
178 | }
179 |
180 | setCallRequest(null)
181 | }
182 |
183 | const signTx = async (tx: any) => {
184 | console.log('user tx', tx)
185 |
186 | tx = await ethers.utils.resolveProperties(tx)
187 |
188 | let version = 0
189 | try {
190 | const iface = new ethers.utils.Interface([
191 | {
192 | constant: true,
193 | inputs: [],
194 | name: 'authereumVersion',
195 | outputs: [
196 | {
197 | name: '',
198 | type: 'string'
199 | }
200 | ],
201 | payable: false,
202 | stateMutability: 'view',
203 | type: 'function'
204 | }
205 | ])
206 | const data = iface.encodeFunctionData('authereumVersion', [])
207 |
208 | let txObj: any = {
209 | from: contractAddress,
210 | to: contractAddress,
211 | value: '0x0',
212 | data: data,
213 | chainId: getNetworkId(networkName)
214 | }
215 |
216 | const result = await provider.call(txObj)
217 | const decoded = iface.decodeFunctionResult('authereumVersion', result)
218 | const _version = Number(decoded[0])
219 | if (!_version) {
220 | throw new Error('Authereum version not found')
221 | }
222 | version = _version
223 | } catch (err) {
224 | const iface = new ethers.utils.Interface([
225 | {
226 | constant: true,
227 | inputs: [],
228 | name: 'version',
229 | outputs: [
230 | {
231 | name: '',
232 | type: 'string'
233 | }
234 | ],
235 | payable: false,
236 | stateMutability: 'view',
237 | type: 'function'
238 | }
239 | ])
240 | const data = iface.encodeFunctionData('version', [])
241 |
242 | let txObj: any = {
243 | from: contractAddress,
244 | to: contractAddress,
245 | value: '0x0',
246 | data: data,
247 | chainId: getNetworkId(networkName)
248 | }
249 |
250 | const result = await provider.call(txObj)
251 | const decoded = iface.decodeFunctionResult('version', result)
252 | const _version = Number(decoded[0])
253 | if (!_version) {
254 | throw new Error('Authereum version not found')
255 | }
256 | version = _version
257 | }
258 |
259 | console.log('account version:', version)
260 |
261 | let data = '0x'
262 | const gasLimit = 300000
263 | if (version <= 2020021700) {
264 | const tx1 = encodeParameters(
265 | ['address', 'uint256', 'uint256', 'bytes'],
266 | [tx.to, tx.value || '0x', gasLimit, tx.data || '0x']
267 | )
268 |
269 | const txs = [tx1]
270 | console.log('user txs array', txs)
271 |
272 | const iface = new ethers.utils.Interface([
273 | {
274 | constant: false,
275 | inputs: [
276 | {
277 | name: '_transactions',
278 | type: 'bytes[]'
279 | }
280 | ],
281 | name: 'executeMultipleMetaTransactions',
282 | outputs: [
283 | {
284 | name: '',
285 | type: 'bytes[]'
286 | }
287 | ],
288 | payable: false,
289 | stateMutability: 'nonpayable',
290 | type: 'function'
291 | }
292 | ])
293 | data = iface.encodeFunctionData('executeMultipleMetaTransactions', [txs])
294 | } else {
295 | const gasLimit = 300000
296 | const tx1 = encodeParameters(
297 | ['address', 'uint256', 'uint256', 'bytes'],
298 | [tx.to, tx.value || '0x', gasLimit, tx.data || '0x']
299 | )
300 |
301 | const txs = [tx1]
302 | console.log('user txs array', txs)
303 |
304 | const iface = new ethers.utils.Interface([
305 | {
306 | constant: false,
307 | inputs: [
308 | {
309 | name: '_transactions',
310 | type: 'bytes[]'
311 | }
312 | ],
313 | name: 'executeMultipleTransactions',
314 | outputs: [
315 | {
316 | name: '',
317 | type: 'bytes[]'
318 | }
319 | ],
320 | payable: false,
321 | stateMutability: 'nonpayable',
322 | type: 'function'
323 | }
324 | ])
325 | data = iface.encodeFunctionData('executeMultipleTransactions', [txs])
326 | }
327 |
328 | const nonce = await provider.getTransactionCount(await wallet.getAddress())
329 |
330 | let txObj: any = {
331 | to: contractAddress,
332 | value: '0x0',
333 | gasLimit,
334 | gasPrice: tx.gasPrice,
335 | data: data,
336 | nonce,
337 | chainId: getNetworkId(networkName)
338 | }
339 |
340 | console.log('contract exec tx', txObj)
341 |
342 | txObj = await ethers.utils.resolveProperties(txObj)
343 | txObj = await wallet.populateTransaction(txObj)
344 |
345 | let hash = ''
346 | if (walletName === 'privateKey') {
347 | const signed = await wallet.signTransaction(txObj)
348 | const { hash: h } = await provider.sendTransaction(signed)
349 | hash = h
350 | } else {
351 | hash = await wallet.sendUncheckedTransaction(txObj)
352 | }
353 |
354 | return hash
355 | }
356 |
357 | const approveCallRequest = async (payload: any) => {
358 | try {
359 | const isTx = ['eth_signTransaction', 'eth_sendTransaction'].includes(
360 | payload.method
361 | )
362 | let result = null
363 |
364 | if (isTx) {
365 | const tx = payload.params[0]
366 | const hash = await signTx(tx)
367 | console.log('TXHASH', hash)
368 | result = {
369 | id: payload.id,
370 | result: hash
371 | }
372 | } else {
373 | console.log('PARAMS', payload.params)
374 | let data = payload.params[0]
375 | let sig = null
376 |
377 | data = hexToUtf8(data)
378 | console.log('DATA', data)
379 | sig = await wallet.signMessage(data)
380 | console.log('SIG', sig)
381 |
382 | result = {
383 | id: payload.id,
384 | result: sig
385 | }
386 | }
387 |
388 | walletConnector.approveRequest(result)
389 | setCallRequest(null)
390 | } catch (err) {
391 | rejectCallRequest({ id: payload.id, error: err.message })
392 | }
393 | }
394 |
395 | const connect = async () => {
396 | const connector = new WalletConnect({
397 | uri: connectUri.trim(),
398 | clientMeta: {
399 | description: 'WalletConnect Developer App',
400 | url: window.location.href,
401 | icons: ['https://walletconnect.org/walletconnect-logo.png'],
402 | name: 'WalletConnect'
403 | }
404 | })
405 |
406 | console.log('CONNECTED', connector.connected)
407 | if (!connector.connected) {
408 | await connector.createSession()
409 | }
410 |
411 | setWalletConnector(connector)
412 | }
413 |
414 | const getSession = () => {
415 | try {
416 | // localStorage 'walletconnect' value is set by walletconnect library
417 | const session = localStorage.getItem('walletconnect')
418 | if (!session) {
419 | return null
420 | }
421 |
422 | return JSON.parse(session)
423 | } catch (err) {
424 | return null
425 | }
426 | }
427 |
428 | useEffect(() => {
429 | const session = getSession()
430 | if (session) {
431 | const walletConnector = new WalletConnect({ session })
432 | setWalletConnector(walletConnector)
433 | }
434 | }, [])
435 |
436 | useEffect(() => {
437 | if (!walletConnector) return
438 | if (!contractAddress) return
439 | console.log('setup Ws')
440 |
441 | const handleConnectWs = async (err: Error, p: any) => {
442 | console.log('connect cb', err, p)
443 | const session = getCachedSession()
444 | setWalletConnectSession(session)
445 | }
446 |
447 | const handleSessionRequest = async (err: Error, payload: any) => {
448 | if (err) {
449 | console.error(err)
450 | return
451 | }
452 |
453 | console.log('session request', payload)
454 | walletConnector?.approveSession({
455 | accounts: [contractAddress],
456 | chainId: getNetworkId(networkName)
457 | })
458 | }
459 |
460 | const handleCallRequest = async (err: Error, payload: any) => {
461 | if (err) {
462 | console.error(err)
463 | return
464 | }
465 |
466 | console.log('call request', payload)
467 | setCallRequest(payload)
468 | }
469 |
470 | const handleDisconnectWs = (err: Error, payload: any) => {
471 | if (err) {
472 | console.error(err)
473 | return
474 | }
475 |
476 | console.log('disconnect', payload)
477 |
478 | setConnectUri('')
479 | setWalletConnectSession(null)
480 | setWalletConnector(null)
481 | }
482 |
483 | ee.on('connect', handleConnectWs)
484 | ee.on('session_request', handleSessionRequest)
485 | ee.on('call_request', handleCallRequest)
486 | ee.on('disconnect', handleDisconnectWs)
487 |
488 | return () => {
489 | ee.off('connect', handleConnectWs)
490 | ee.off('session_request', handleSessionRequest)
491 | ee.off('call_request', handleCallRequest)
492 | ee.off('disconnect', handleDisconnectWs)
493 | }
494 | }, [ee, walletConnector, contractAddress, networkName])
495 |
496 | const handleWalletDisconnect = async (event: any) => {
497 | try {
498 | await onboard.reset()
499 | } catch (err) {
500 | console.error(err)
501 | }
502 | setWallet(null)
503 | setWalletName(null)
504 | localStorage.removeItem('walletName')
505 | setContractAddress('')
506 | handleWalletConnectDisconnect(event)
507 | }
508 |
509 | const handleWalletConnectDisconnect = async (event: any) => {
510 | setConnectUri('')
511 |
512 | try {
513 | if (walletConnector) {
514 | walletConnector.killSession()
515 | setWalletConnector(null)
516 | }
517 | } catch (err) {
518 | console.error(err)
519 | }
520 | }
521 |
522 | const handleContractAddressChange = (event: any) => {
523 | const contractAddress = event.target.value
524 | setContractAddress(contractAddress)
525 | localStorage.setItem('contractAddress', contractAddress)
526 | }
527 |
528 | onboard.on('walletChange', (wallet: any) => {
529 | let { name } = wallet
530 | if (!name) return
531 |
532 | localStorage.setItem('walletName', name)
533 | setWalletName(name)
534 | })
535 |
536 | onboard.on('accountChange', (address: any) => {})
537 |
538 | useEffect(() => {
539 | const setup = async () => {
540 | if (!wallet) return
541 | const address = await wallet.getAddress()
542 | setInfo({
543 | address
544 | })
545 | }
546 |
547 | setup()
548 | }, [wallet])
549 | const handleEditClick = (event: any) => {
550 | setCallRequestEditable(true)
551 | }
552 | const updateCallRequest = (event: any) => {
553 | const value = event.target.value
554 | try {
555 | setCallRequest(JSON.parse(value))
556 | } catch (err) {
557 | console.error(err)
558 | }
559 | }
560 | const handleEditBlur = (event: any) => {
561 | setCallRequestEditable(false)
562 | }
563 |
564 | const updateNetworkName = (event: any) => {
565 | setNetworkName(event.target.value)
566 | }
567 | const updateCustomTx = (event: any) => {
568 | setCustomTx(event.target.value)
569 | }
570 | useEffect(() => {
571 | localStorage.setItem('customTx', customTx)
572 | }, [customTx])
573 | const handleCustomTxSubmit = async (event: any) => {
574 | event.preventDefault()
575 | try {
576 | setCustomTxHash('')
577 | const hash = await signTx(JSON.parse(customTx))
578 | setCustomTxHash(hash)
579 | } catch (err) {
580 | alert(err.message)
581 | }
582 | }
583 | const renderNetworkSelect = () => {
584 | return (
585 |
592 | )
593 | }
594 | const renderCustomTxForm = () => {
595 | return (
596 |
597 |
609 |
610 | )
611 | }
612 | const renderConnected = () => {
613 | if (!wallet) return null
614 | return (
615 | <>
616 | Wallet Name: {walletName}
617 |
618 | {info.address && (
619 |
620 | Connected address (admin key): {info.address}
621 |
622 | )}
623 | {contractAddress && (
624 |
625 | Authereum account address: {contractAddress}
626 |
627 | )}
628 | {!walletConnectSession && (
629 | <>
630 | {contractAddress && (
631 |
632 |
633 |
634 |
642 |
649 |
650 |
or
651 | {renderCustomTxForm()}
652 |
653 | )}
654 | >
655 | )}
656 |
657 | {!contractAddress && (
658 |
659 |
660 |
668 |
669 | )}
670 | {walletConnectSession && (
671 |
672 |
687 |
688 | {callRequest ? (
689 |
690 | {callRequestEditable ? (
691 |
700 | ) : (
701 |
702 |
703 | click to edit
704 |
705 |
711 | {JSON.stringify(callRequest, null, 2)}
712 |
713 |
714 | )}
715 |
722 |
729 |
730 | ) : (
731 |
Awaiting call requests...
732 | )}
733 |
734 |
735 | )}
736 |
737 | {walletConnectSession && (
738 |
739 |
746 |
747 | )}
748 |
749 |
756 |
757 |
758 | >
759 | )
760 | }
761 | const renderDisconnected = () => {
762 | if (wallet) return null
763 | return (
764 |
765 |
766 |
769 |
770 |
or
771 |
772 |
781 |
782 |
783 | )
784 | }
785 | return (
786 |
787 |
788 |
Authereum Direct
789 | {renderNetworkSelect()}
790 |
791 |
792 |
793 | {renderConnected()}
794 | {renderDisconnected()}
795 |
796 |
797 |
798 | )
799 | }
800 |
801 | export default App
802 |
--------------------------------------------------------------------------------
/src/Onboard.ts:
--------------------------------------------------------------------------------
1 | import OnboardSDK from 'bnc-onboard'
2 | import { ethers } from 'ethers'
3 | import { EventEmitter } from 'events'
4 |
5 | import {
6 | blocknativeDappId,
7 | fortmaticApiKey,
8 | portisDappId,
9 | squarelinkClientId
10 | } from './config'
11 |
12 | interface IOptions {
13 | networkId?: number
14 | rpcUrl?: string
15 | }
16 |
17 | class Onboard extends EventEmitter {
18 | _provider: any
19 | _onboard: any
20 | _selectedWallet: any
21 |
22 | constructor (options: IOptions = {}) {
23 | super()
24 |
25 | const { networkId, rpcUrl } = options
26 |
27 | const wallets = [
28 | // { walletName: 'authereum', preferred: true },
29 | {
30 | walletName: 'walletConnect',
31 | infuraKey: '8e4fe7af961f48a1958584ec36742b44',
32 | preferred: true
33 | },
34 | { walletName: 'metamask', preferred: true },
35 | { walletName: 'ledger', rpcUrl: rpcUrl, preferred: true },
36 | {
37 | walletName: 'trezor',
38 | appUrl: 'authereum.com',
39 | email: 'contract@authereum.com',
40 | rpcUrl: rpcUrl,
41 | preferred: true
42 | },
43 | { walletName: 'dapper' },
44 | { walletName: 'fortmatic', apiKey: fortmaticApiKey },
45 | { walletName: 'portis', apiKey: portisDappId, label: 'Portis' },
46 | { walletName: 'torus' },
47 | { walletName: 'squarelink', apiKey: squarelinkClientId },
48 | { walletName: 'coinbase' },
49 | { walletName: 'trust', rpcUrl: rpcUrl },
50 | { walletName: 'opera' },
51 | { walletName: 'operaTouch' },
52 | { walletName: 'status' },
53 | { walletName: 'imToken', rpcUrl: rpcUrl }
54 | ]
55 |
56 | const opts: any = {
57 | dappId: blocknativeDappId,
58 | networkId,
59 | walletSelect: {
60 | wallets: wallets
61 | },
62 | subscriptions: {
63 | address: this.handleAddressChange,
64 | wallet: this.handleWalletChange
65 | },
66 | walletCheck: [
67 | { checkName: 'derivationPath' },
68 | { checkName: 'connect' },
69 | { checkName: 'accounts' },
70 | { checkName: 'network' }
71 | ]
72 | }
73 |
74 | this._onboard = OnboardSDK(opts)
75 | }
76 |
77 | handleAddressChange = async (address: string) => {
78 | this.emit('accountChange', address)
79 | }
80 |
81 | handleWalletChange = async (wallet: any) => {
82 | const { name, provider } = wallet
83 |
84 | await this._onboard.walletCheck()
85 |
86 | this._selectedWallet = name
87 | if (provider) {
88 | this._provider = provider
89 | }
90 | const address = await this.getAddress()
91 | this.emit('walletChange', { name, address })
92 | }
93 |
94 | async walletCheck (walletName?: string) {
95 | await this._onboard.walletSelect(walletName)
96 | await this._onboard.walletCheck()
97 | }
98 |
99 | async showWalletSector () {
100 | await this._onboard.walletSelect()
101 | await this._onboard.walletCheck()
102 | }
103 |
104 | async reset () {
105 | return this._onboard.walletReset()
106 | }
107 |
108 | getState () {
109 | return this._onboard.getState()
110 | }
111 |
112 | getWalletName () {
113 | return this._selectedWallet
114 | }
115 |
116 | getAddress () {
117 | const signer = this.getSigner()
118 | return signer.getAddress()
119 | }
120 |
121 | getProvider () {
122 | return this._provider
123 | }
124 |
125 | getEthersProvider () {
126 | return new ethers.providers.Web3Provider(this.getProvider())
127 | }
128 |
129 | getSigner () {
130 | return this.getEthersProvider().getSigner()
131 | }
132 |
133 | setConfig (config: any) {
134 | return this._onboard.config(config)
135 | }
136 | }
137 |
138 | export default Onboard
139 |
--------------------------------------------------------------------------------
/src/abi/AuthereumAccount.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "anonymous": false,
4 | "inputs": [
5 | {
6 | "indexed": true,
7 | "internalType": "address",
8 | "name": "authKey",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "AuthKeyAdded",
13 | "type": "event"
14 | },
15 | {
16 | "anonymous": false,
17 | "inputs": [
18 | {
19 | "indexed": true,
20 | "internalType": "address",
21 | "name": "authKey",
22 | "type": "address"
23 | }
24 | ],
25 | "name": "AuthKeyRemoved",
26 | "type": "event"
27 | },
28 | {
29 | "anonymous": false,
30 | "inputs": [
31 | {
32 | "indexed": false,
33 | "internalType": "string",
34 | "name": "reason",
35 | "type": "string"
36 | }
37 | ],
38 | "name": "CallFailed",
39 | "type": "event"
40 | },
41 | {
42 | "anonymous": false,
43 | "inputs": [
44 | {
45 | "indexed": true,
46 | "internalType": "address",
47 | "name": "implementation",
48 | "type": "address"
49 | }
50 | ],
51 | "name": "Upgraded",
52 | "type": "event"
53 | },
54 | {
55 | "payable": true,
56 | "stateMutability": "payable",
57 | "type": "fallback"
58 | },
59 | {
60 | "constant": false,
61 | "inputs": [
62 | {
63 | "internalType": "address",
64 | "name": "_authKey",
65 | "type": "address"
66 | }
67 | ],
68 | "name": "addAuthKey",
69 | "outputs": [],
70 | "payable": false,
71 | "stateMutability": "nonpayable",
72 | "type": "function"
73 | },
74 | {
75 | "constant": true,
76 | "inputs": [
77 | {
78 | "internalType": "address",
79 | "name": "",
80 | "type": "address"
81 | }
82 | ],
83 | "name": "authKeys",
84 | "outputs": [
85 | {
86 | "internalType": "bool",
87 | "name": "",
88 | "type": "bool"
89 | }
90 | ],
91 | "payable": false,
92 | "stateMutability": "view",
93 | "type": "function"
94 | },
95 | {
96 | "constant": true,
97 | "inputs": [],
98 | "name": "authereumVersion",
99 | "outputs": [
100 | {
101 | "internalType": "string",
102 | "name": "",
103 | "type": "string"
104 | }
105 | ],
106 | "payable": false,
107 | "stateMutability": "view",
108 | "type": "function"
109 | },
110 | {
111 | "constant": false,
112 | "inputs": [
113 | {
114 | "internalType": "bytes[]",
115 | "name": "_transactions",
116 | "type": "bytes[]"
117 | },
118 | {
119 | "internalType": "uint256",
120 | "name": "_gasPrice",
121 | "type": "uint256"
122 | },
123 | {
124 | "internalType": "uint256",
125 | "name": "_gasOverhead",
126 | "type": "uint256"
127 | },
128 | {
129 | "internalType": "address",
130 | "name": "_feeTokenAddress",
131 | "type": "address"
132 | },
133 | {
134 | "internalType": "uint256",
135 | "name": "_feeTokenRate",
136 | "type": "uint256"
137 | },
138 | {
139 | "internalType": "bytes",
140 | "name": "_transactionMessageHashSignature",
141 | "type": "bytes"
142 | }
143 | ],
144 | "name": "executeMultipleAuthKeyMetaTransactions",
145 | "outputs": [
146 | {
147 | "internalType": "bytes[]",
148 | "name": "",
149 | "type": "bytes[]"
150 | }
151 | ],
152 | "payable": false,
153 | "stateMutability": "nonpayable",
154 | "type": "function"
155 | },
156 | {
157 | "constant": false,
158 | "inputs": [
159 | {
160 | "internalType": "bytes[]",
161 | "name": "_transactions",
162 | "type": "bytes[]"
163 | },
164 | {
165 | "internalType": "uint256",
166 | "name": "_gasPrice",
167 | "type": "uint256"
168 | },
169 | {
170 | "internalType": "uint256",
171 | "name": "_gasOverhead",
172 | "type": "uint256"
173 | },
174 | {
175 | "internalType": "bytes",
176 | "name": "_loginKeyRestrictionsData",
177 | "type": "bytes"
178 | },
179 | {
180 | "internalType": "address",
181 | "name": "_feeTokenAddress",
182 | "type": "address"
183 | },
184 | {
185 | "internalType": "uint256",
186 | "name": "_feeTokenRate",
187 | "type": "uint256"
188 | },
189 | {
190 | "internalType": "bytes",
191 | "name": "_transactionMessageHashSignature",
192 | "type": "bytes"
193 | },
194 | {
195 | "internalType": "bytes",
196 | "name": "_loginKeyAttestationSignature",
197 | "type": "bytes"
198 | }
199 | ],
200 | "name": "executeMultipleLoginKeyMetaTransactions",
201 | "outputs": [
202 | {
203 | "internalType": "bytes[]",
204 | "name": "",
205 | "type": "bytes[]"
206 | }
207 | ],
208 | "payable": false,
209 | "stateMutability": "nonpayable",
210 | "type": "function"
211 | },
212 | {
213 | "constant": false,
214 | "inputs": [
215 | {
216 | "internalType": "bytes[]",
217 | "name": "_transactions",
218 | "type": "bytes[]"
219 | }
220 | ],
221 | "name": "executeMultipleMetaTransactions",
222 | "outputs": [
223 | {
224 | "internalType": "bytes[]",
225 | "name": "",
226 | "type": "bytes[]"
227 | }
228 | ],
229 | "payable": false,
230 | "stateMutability": "nonpayable",
231 | "type": "function"
232 | },
233 | {
234 | "constant": true,
235 | "inputs": [],
236 | "name": "getChainId",
237 | "outputs": [
238 | {
239 | "internalType": "uint256",
240 | "name": "",
241 | "type": "uint256"
242 | }
243 | ],
244 | "payable": false,
245 | "stateMutability": "pure",
246 | "type": "function"
247 | },
248 | {
249 | "constant": false,
250 | "inputs": [
251 | {
252 | "internalType": "address",
253 | "name": "_authKey",
254 | "type": "address"
255 | }
256 | ],
257 | "name": "initializeV1",
258 | "outputs": [],
259 | "payable": false,
260 | "stateMutability": "nonpayable",
261 | "type": "function"
262 | },
263 | {
264 | "constant": false,
265 | "inputs": [
266 | {
267 | "internalType": "uint256",
268 | "name": "_deploymentCost",
269 | "type": "uint256"
270 | }
271 | ],
272 | "name": "initializeV2",
273 | "outputs": [],
274 | "payable": false,
275 | "stateMutability": "nonpayable",
276 | "type": "function"
277 | },
278 | {
279 | "constant": true,
280 | "inputs": [
281 | {
282 | "internalType": "bytes",
283 | "name": "_data",
284 | "type": "bytes"
285 | },
286 | {
287 | "internalType": "bytes",
288 | "name": "_signature",
289 | "type": "bytes"
290 | }
291 | ],
292 | "name": "isValidAuthKeySignature",
293 | "outputs": [
294 | {
295 | "internalType": "bytes4",
296 | "name": "",
297 | "type": "bytes4"
298 | }
299 | ],
300 | "payable": false,
301 | "stateMutability": "view",
302 | "type": "function"
303 | },
304 | {
305 | "constant": true,
306 | "inputs": [
307 | {
308 | "internalType": "bytes",
309 | "name": "_data",
310 | "type": "bytes"
311 | },
312 | {
313 | "internalType": "bytes",
314 | "name": "_signature",
315 | "type": "bytes"
316 | }
317 | ],
318 | "name": "isValidLoginKeySignature",
319 | "outputs": [
320 | {
321 | "internalType": "bytes4",
322 | "name": "",
323 | "type": "bytes4"
324 | }
325 | ],
326 | "payable": false,
327 | "stateMutability": "view",
328 | "type": "function"
329 | },
330 | {
331 | "constant": true,
332 | "inputs": [
333 | {
334 | "internalType": "bytes",
335 | "name": "_data",
336 | "type": "bytes"
337 | },
338 | {
339 | "internalType": "bytes",
340 | "name": "_signature",
341 | "type": "bytes"
342 | }
343 | ],
344 | "name": "isValidSignature",
345 | "outputs": [
346 | {
347 | "internalType": "bytes4",
348 | "name": "",
349 | "type": "bytes4"
350 | }
351 | ],
352 | "payable": false,
353 | "stateMutability": "view",
354 | "type": "function"
355 | },
356 | {
357 | "constant": true,
358 | "inputs": [],
359 | "name": "lastInitializedVersion",
360 | "outputs": [
361 | {
362 | "internalType": "uint256",
363 | "name": "",
364 | "type": "uint256"
365 | }
366 | ],
367 | "payable": false,
368 | "stateMutability": "view",
369 | "type": "function"
370 | },
371 | {
372 | "constant": true,
373 | "inputs": [],
374 | "name": "nonce",
375 | "outputs": [
376 | {
377 | "internalType": "uint256",
378 | "name": "",
379 | "type": "uint256"
380 | }
381 | ],
382 | "payable": false,
383 | "stateMutability": "view",
384 | "type": "function"
385 | },
386 | {
387 | "constant": true,
388 | "inputs": [],
389 | "name": "numAuthKeys",
390 | "outputs": [
391 | {
392 | "internalType": "uint256",
393 | "name": "",
394 | "type": "uint256"
395 | }
396 | ],
397 | "payable": false,
398 | "stateMutability": "view",
399 | "type": "function"
400 | },
401 | {
402 | "constant": false,
403 | "inputs": [
404 | {
405 | "internalType": "address",
406 | "name": "",
407 | "type": "address"
408 | },
409 | {
410 | "internalType": "address",
411 | "name": "",
412 | "type": "address"
413 | },
414 | {
415 | "internalType": "uint256[]",
416 | "name": "",
417 | "type": "uint256[]"
418 | },
419 | {
420 | "internalType": "uint256[]",
421 | "name": "",
422 | "type": "uint256[]"
423 | },
424 | {
425 | "internalType": "bytes",
426 | "name": "",
427 | "type": "bytes"
428 | }
429 | ],
430 | "name": "onERC1155BatchReceived",
431 | "outputs": [
432 | {
433 | "internalType": "bytes4",
434 | "name": "",
435 | "type": "bytes4"
436 | }
437 | ],
438 | "payable": false,
439 | "stateMutability": "nonpayable",
440 | "type": "function"
441 | },
442 | {
443 | "constant": false,
444 | "inputs": [
445 | {
446 | "internalType": "address",
447 | "name": "",
448 | "type": "address"
449 | },
450 | {
451 | "internalType": "address",
452 | "name": "",
453 | "type": "address"
454 | },
455 | {
456 | "internalType": "uint256",
457 | "name": "",
458 | "type": "uint256"
459 | },
460 | {
461 | "internalType": "uint256",
462 | "name": "",
463 | "type": "uint256"
464 | },
465 | {
466 | "internalType": "bytes",
467 | "name": "",
468 | "type": "bytes"
469 | }
470 | ],
471 | "name": "onERC1155Received",
472 | "outputs": [
473 | {
474 | "internalType": "bytes4",
475 | "name": "",
476 | "type": "bytes4"
477 | }
478 | ],
479 | "payable": false,
480 | "stateMutability": "nonpayable",
481 | "type": "function"
482 | },
483 | {
484 | "constant": false,
485 | "inputs": [
486 | {
487 | "internalType": "address",
488 | "name": "",
489 | "type": "address"
490 | },
491 | {
492 | "internalType": "address",
493 | "name": "",
494 | "type": "address"
495 | },
496 | {
497 | "internalType": "uint256",
498 | "name": "",
499 | "type": "uint256"
500 | },
501 | {
502 | "internalType": "bytes",
503 | "name": "",
504 | "type": "bytes"
505 | }
506 | ],
507 | "name": "onERC721Received",
508 | "outputs": [
509 | {
510 | "internalType": "bytes4",
511 | "name": "",
512 | "type": "bytes4"
513 | }
514 | ],
515 | "payable": false,
516 | "stateMutability": "nonpayable",
517 | "type": "function"
518 | },
519 | {
520 | "constant": false,
521 | "inputs": [
522 | {
523 | "internalType": "address",
524 | "name": "_authKey",
525 | "type": "address"
526 | }
527 | ],
528 | "name": "removeAuthKey",
529 | "outputs": [],
530 | "payable": false,
531 | "stateMutability": "nonpayable",
532 | "type": "function"
533 | },
534 | {
535 | "constant": false,
536 | "inputs": [
537 | {
538 | "internalType": "address",
539 | "name": "_newImplementation",
540 | "type": "address"
541 | },
542 | {
543 | "internalType": "bytes",
544 | "name": "_data",
545 | "type": "bytes"
546 | }
547 | ],
548 | "name": "upgradeToAndCall",
549 | "outputs": [],
550 | "payable": false,
551 | "stateMutability": "nonpayable",
552 | "type": "function"
553 | }
554 | ]
555 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import getNetworkId from '@authereum/utils/core/getNetworkId'
2 | import getRpcProviderUrl from '@authereum/utils/core/getRpcProviderUrl'
3 |
4 | const networkName = process.env.REACT_APP_NETWORK || 'mainnet'
5 | const isMainnet = networkName === 'mainnet'
6 |
7 | const blocknativeDappId = process.env.REACT_APP_BLOCKNATIVE_DAPP_ID
8 | const squarelinkClientId = process.env.REACT_APP_SQUARELINK_CLIENT_ID
9 | const portisDappId = process.env.REACT_APP_PORTIS_DAPP_ID
10 | let fortmaticApiKey = process.env.REACT_APP_FORTMATIC_API_KEY
11 |
12 | const networkId = getNetworkId(networkName)
13 | const rpcUri = getRpcProviderUrl(networkName)
14 |
15 | export {
16 | blocknativeDappId,
17 | squarelinkClientId,
18 | fortmaticApiKey,
19 | portisDappId,
20 | networkId,
21 | rpcUri,
22 | isMainnet
23 | }
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9 | sans-serif;
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | background: #eee;
13 | }
14 |
15 | code {
16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
17 | monospace;
18 | }
19 |
20 | label {
21 | display: block;
22 | }
23 |
24 | pre {
25 | word-break: break-all;
26 | white-space: break-spaces;
27 | }
28 |
29 | textarea {
30 | width: 100%;
31 | height: 200px;
32 | }
33 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 | import * as serviceWorker from './serviceWorker'
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | )
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister()
18 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | )
22 |
23 | export function register (config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config)
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | )
48 | })
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config)
52 | }
53 | })
54 | }
55 | }
56 |
57 | function registerValidSW (swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing
63 | if (installingWorker == null) {
64 | return
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | )
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration)
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.')
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration)
90 | }
91 | }
92 | }
93 | }
94 | }
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error)
98 | })
99 | }
100 |
101 | function checkValidServiceWorker (swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type')
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload()
117 | })
118 | })
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config)
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | )
128 | })
129 | }
130 |
131 | export function unregister () {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister()
136 | })
137 | .catch(error => {
138 | console.error(error.message)
139 | })
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect'
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "react"
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------