├── .env ├── tsconfig.prod.json ├── public ├── favicon.ico ├── favicon.ico.png ├── assets │ └── sponsors │ │ ├── eosimpera.png │ │ └── eosnation.png ├── manifest.json ├── index.html └── favicon.svg ├── tsconfig.test.json ├── images.d.ts ├── src ├── containers │ ├── App.test.tsx │ ├── App.css │ ├── InfoPage.tsx │ ├── logo.svg │ ├── eosfilestore.svg │ ├── UploadPage.tsx │ ├── DownloadPage.tsx │ └── App.tsx ├── index.css ├── stores │ ├── user.tsx │ ├── index.tsx │ ├── notification.tsx │ └── file.tsx ├── eosfilestore │ ├── utils.ts │ ├── costants.ts │ └── core.ts ├── index.tsx └── registerServiceWorker.ts ├── .gitignore ├── README.md ├── tslint.json ├── tsconfig.json ├── LICENSE └── package.json /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_VERSION=$npm_package_version -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grigio/eosfilestore-web/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grigio/eosfilestore-web/HEAD/public/favicon.ico.png -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /public/assets/sponsors/eosimpera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grigio/eosfilestore-web/HEAD/public/assets/sponsors/eosimpera.png -------------------------------------------------------------------------------- /public/assets/sponsors/eosnation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grigio/eosfilestore-web/HEAD/public/assets/sponsors/eosnation.png -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | 5 | declare module 'eosjs' 6 | declare module 'scatter-js' -------------------------------------------------------------------------------- /src/containers/App.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,700'); 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | /* font-family: 'Nunito Sans', sans-serif; */ 6 | } 7 | 8 | /* Mainly for NavBar */ 9 | .bp3-button-text, * { 10 | font-family: 'Nunito Sans', sans-serif; 11 | font-size: 17px; 12 | } -------------------------------------------------------------------------------- /src/stores/user.tsx: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx' 2 | 3 | interface IAccount { 4 | name: string 5 | } 6 | 7 | class UserStore { 8 | @observable account: IAccount | null = null 9 | 10 | @action setAccount(account: any) { 11 | this.account = account 12 | } 13 | 14 | } 15 | export const userStore = new UserStore() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /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 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/stores/index.tsx: -------------------------------------------------------------------------------- 1 | import createBrowserHistory from 'history/createBrowserHistory'; 2 | import { RouterStore } from 'mobx-react-router' 3 | import { fileStore } from './file' 4 | import { userStore } from './user' 5 | import { notificationStore } from './notification' 6 | 7 | 8 | 9 | export const browserHistory = createBrowserHistory(); 10 | export const routingStore = new RouterStore(); 11 | export { fileStore, userStore, notificationStore } -------------------------------------------------------------------------------- /src/stores/notification.tsx: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx' 2 | // import { fetchTx } from '../eosfilestore/core' 3 | 4 | export interface INotification { 5 | title?: string 6 | message: string 7 | } 8 | 9 | class NotificationStore { 10 | @observable notifications: INotification[] = [] 11 | 12 | @action 13 | push(notification: INotification) { 14 | this.notifications.push(notification) 15 | } 16 | 17 | @action clear() { 18 | this.notifications = [] 19 | } 20 | 21 | } 22 | export const notificationStore = new NotificationStore() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EOSfilestore Web 2 | 3 | Browser version of [EOSfilestore](https://github.com/grigio/eosfilestore#-eosfilestore) cli 4 | 5 | ### Features and TODO 6 | 7 | - [x] Base React + Mobx + Router 8 | - [x] Download from txid in url 9 | - [x] Implemented download from txid 10 | - [ ] Share the same eosfilestore node/javascript 11 | - [x] Scatter integration 12 | - [x] Implement upload 13 | - [ ] Estimation of CPU and NET before the upload 14 | - [ ] Responsive mobile 15 | - [ ] Better txid progress visualization 16 | - [ ] Clean up UI / Code / tests / types ... 17 | 18 | ### Donate 19 | 20 | If you want to support the development you can donate to `@eosfilestore` on EOS. 21 | 22 | ### License 23 | 24 | EOSfilestore is [MIT licensed](./LICENSE). -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "rules": { 4 | "ordered-imports": false, 5 | "member-access": false, 6 | "jsx-no-lambda": false, 7 | "object-literal-sort-keys": false, 8 | "no-debugger": false, 9 | "no-console": { 10 | "severity": "none", 11 | "options": [ 12 | "debug", 13 | "info", 14 | "log", 15 | "time", 16 | "timeEnd", 17 | "trace" 18 | ] 19 | }, 20 | "semicolon": [ 21 | false, 22 | "never" 23 | ], 24 | "no-implicit-dependencies": [ 25 | true, 26 | "dev" 27 | ] 28 | }, 29 | "linterOptions": { 30 | "exclude": [ 31 | "config/**/*.js", 32 | "node_modules/**/*.ts" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "experimentalDecorators": true, 20 | "noUnusedLocals": true 21 | }, 22 | "exclude": [ 23 | "node_modules", 24 | "build", 25 | "scripts", 26 | "acceptance-tests", 27 | "webpack", 28 | "jest", 29 | "src/setupTests.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Luigi Maselli 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eosfilestore-web", 3 | "version": "0.1.1", 4 | "private": true, 5 | "dependencies": { 6 | "@blueprintjs/core": "^3.0.1", 7 | "base64-js": "^1.3.0", 8 | "eosjs": "^16.0.0", 9 | "flexboxgrid-sass": "^8.0.5", 10 | "history": "^4.7.2", 11 | "mobx": "^5.0.3", 12 | "mobx-react": "^5.2.3", 13 | "mobx-react-router": "^4.0.4", 14 | "react": "^16.4.1", 15 | "react-dom": "^16.4.1", 16 | "react-router": "^4.3.1", 17 | "react-router-dom": "^4.3.1", 18 | "react-scripts-ts": "2.16.0", 19 | "scatter-js": "^2.1.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts-ts start", 23 | "build": "react-scripts-ts build", 24 | "test": "react-scripts-ts test --env=jsdom", 25 | "deploy": "now deploy --public", 26 | "eject": "react-scripts-ts eject" 27 | }, 28 | "devDependencies": { 29 | "@types/history": "^4.6.2", 30 | "@types/jest": "^23.3.0", 31 | "@types/node": "^10.5.2", 32 | "@types/react": "^16.4.6", 33 | "@types/react-dom": "^16.0.6", 34 | "@types/react-router": "^4.0.29", 35 | "@types/react-router-dom": "^4.2.7", 36 | "typescript": "^2.9.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/eosfilestore/utils.ts: -------------------------------------------------------------------------------- 1 | import { maxPayloadSize } from './costants' 2 | 3 | // it splits the string in string chunks of a fixed size 4 | export function splitString(str: string, size?: number): string[] { 5 | size = size || maxPayloadSize 6 | const re = new RegExp('.{1,' + size + '}', 'g') 7 | const matches = str.match(re) || [] 8 | return matches 9 | } 10 | 11 | // it create the memo to add to the tx 12 | export function createPayload(chunk: string, nextTx: string | null): string { 13 | if (chunk === '') { throw new Error(`chunk can't be empty`) } 14 | const nextTxStr = (nextTx) ? `"${nextTx}"` : null 15 | return `{"c":"${chunk}","n":${nextTxStr}}` 16 | } 17 | 18 | export function b64DecodeUnicode(str: string) { 19 | // Going backwards: from bytestream, to percent-encoding, to original string. 20 | return decodeURIComponent(atob(str).split('').map((c) => { 21 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 22 | }).join('')); 23 | } 24 | 25 | export function arrayBufferToBase64(buffer:any, mimetype:string) { 26 | let binary = ''; 27 | const bytes = new Uint8Array(buffer); 28 | const len = bytes.byteLength; 29 | for (let i = 0; i < len; i++) { 30 | binary += String.fromCharCode(bytes[i]); 31 | } 32 | return `data:${mimetype};base64,${window.btoa(binary)}`; 33 | } -------------------------------------------------------------------------------- /src/containers/App.css: -------------------------------------------------------------------------------- 1 | @import "~@blueprintjs/core/lib/css/blueprint.css"; 2 | @import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; 3 | @import "~flexboxgrid-sass/dist/flexboxgrid.css"; 4 | 5 | .App { 6 | text-align: center; 7 | } 8 | 9 | .App-logo { 10 | height: 200px; 11 | } 12 | 13 | .App-header { 14 | background-color: rgb(184, 184, 184); 15 | /* height: 150px; */ 16 | /* padding: 20px; */ 17 | color: white; 18 | display: block; 19 | width: 100%; 20 | } 21 | 22 | .App-title { 23 | font-size: 1.5em; 24 | } 25 | 26 | .App-intro { 27 | font-size: large; 28 | } 29 | 30 | .App-wrapper { 31 | /* width: 1024px; */ 32 | /* background-color: antiquewhite; */ 33 | width: 100%; 34 | } 35 | 36 | .App-footer { 37 | min-height: 200px; 38 | background: rgb(216, 216, 216); 39 | } 40 | 41 | .fill { 42 | width: 100%; 43 | } 44 | 45 | .rowspace { 46 | /* background: #eee; */ 47 | margin: 3px 3px; 48 | padding: 5px 5px; 49 | 50 | } 51 | 52 | .bp3-navbar-group a:hover { 53 | text-decoration-line: none; 54 | } 55 | 56 | /* .bp3-navbar { 57 | box-shadow: 0em; 58 | -webkit-box-shadow: 0em; 59 | } */ 60 | .pulse-animation { 61 | animation: pulse 3s infinite; 62 | } 63 | 64 | @keyframes pulse { 65 | 0% { 66 | color: rgb(1, 67, 133); 67 | } 68 | 50% { 69 | color: rgb(0, 4, 255); 70 | } 71 | 100% { 72 | color: rgb(1, 67, 133); 73 | } 74 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Provider } from 'mobx-react'; 4 | import { syncHistoryWithStore } from 'mobx-react-router'; 5 | import { Router, Route } from 'react-router'; 6 | import { Redirect } from 'react-router-dom' 7 | 8 | import App from './containers/App'; 9 | import DownloadPage from './containers/DownloadPage' 10 | import UploadPage from './containers/UploadPage' 11 | 12 | import InfoPage from './containers/InfoPage' 13 | 14 | import { routingStore, browserHistory, fileStore, userStore, notificationStore } from './stores' 15 | import registerServiceWorker from './registerServiceWorker'; 16 | 17 | import './index.css'; 18 | 19 | // Stores 20 | 21 | const stores = { 22 | userStore, 23 | fileStore, 24 | notificationStore, 25 | routing: routingStore, 26 | }; 27 | 28 | const history = syncHistoryWithStore(browserHistory, routingStore); 29 | 30 | ReactDOM.render( 31 | 32 | 33 | <> 34 | 35 | 36 | 37 | 38 | 39 | 40 | ( 41 | 42 | )} /> 43 | 44 | 45 | 46 | 47 | , 48 | document.getElementById('root') as HTMLElement 49 | ); 50 | 51 | registerServiceWorker(); 52 | -------------------------------------------------------------------------------- /src/eosfilestore/costants.ts: -------------------------------------------------------------------------------- 1 | // import fs from 'fs' 2 | 3 | // function loadConfig() { 4 | // const confDir = `${process.env.HOME}/.eosfilestore` 5 | 6 | // // write default config.js in the first run 7 | // if (!fs.existsSync(confDir)) { 8 | // fs.mkdirSync(confDir) 9 | // fs.writeFileSync(`${confDir}/config.json`, ` 10 | // { 11 | // "from":"", 12 | // "privateKey":"", 13 | // "permission":"active" 14 | // } 15 | // `) 16 | // console.log(`eosfilestore: Generated '${confDir}/config.json', please edit with your EOS keys`) 17 | // } 18 | 19 | // // read config 20 | // // const configFile = fs.readFileSync(`${confDir}/config.json`).toLocaleString() 21 | // const configFile = `{ 22 | // "from":"", 23 | // "privateKey":"", 24 | // "permission":"active" 25 | // }` 26 | 27 | // return JSON.parse(configFile) 28 | 29 | // } 30 | 31 | const confObj = { 32 | chainId: null, 33 | "from": "", 34 | "privateKey": "", 35 | "permission": "active", 36 | } 37 | 38 | // console.log('cc', confObj) 39 | 40 | export const maxPayloadSize = 10000 // 429496 // 176 // 4294967294 41 | export const netPerTx = 1272 42 | export const cpuPerTx = 20000 // it depends on net congestion 43 | 44 | 45 | // export const chainId = confObj.chainId || 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' // mainnet 46 | // export const wif = confObj.privateKey 47 | // export const from = confObj.from 48 | export const permission = confObj.permission || 'active' 49 | 50 | function networkConfigFn() { 51 | const nc = { 52 | blockchain: 'eos', 53 | protocol: 'https', 54 | host: 'eos.greymass.com', 55 | port: 443, 56 | chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' 57 | } 58 | return { 59 | ...nc, 60 | httpEndpoint: `${nc.protocol}://${nc.host}:${nc.port}` 61 | } 62 | } 63 | 64 | export const networkConfig: any = networkConfigFn() -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 33 | EOSfilestore Web 34 | 35 | 36 | 39 |
40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/containers/InfoPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import * as ReactDOM from 'react-dom'; 3 | import { observer } from 'mobx-react'; 4 | // import { counterStore, routingStore } from '../stores' 5 | 6 | // gif b5407140a0f7b6365b2dcf1731f6ad50ee0502d052ecdb9da01700ecd398f759 7 | // a jpg 90edc50fac5a622820404e24f6839c7b9ca2bff73a3b1a11221caf85334aa6e6 8 | // sh 987c4c9995e717df08ce82f4b24a2f814749f92113e7458124f29607cad0b77d 9 | // welcome txt 9a9e4d3637cbecea36d7cf54d0cf8a7e8046f0b893a1d880800ec8312c7d9eb4 10 | 11 | @observer 12 | class InfoPage extends React.Component{ 13 | 14 | render() { 15 | 16 | 17 | 18 | return ( 19 | <> 20 |
21 |
22 |
23 | 24 |

Info & FAQ

25 | 26 |

Why should I store a file on EOS blockchain?

27 |

You don't have to, but if you do it, you have interesting features: 28 |

29 |
    30 |
  • Immutability: Once you upload a file on the Blockchain it can't be edited.
  • 31 |
  • Time proof: You can claim you had a file in a precise moment of the past. And the transaction is is your proof.
  • 32 |
  • It's free: It doesn't cost EOS to upload a file, but you need enough EOS in staking in CPU and NET.
  • 33 |
34 | 35 |

How does EOSfilestore work?

36 |

37 | EOSfilestore split the file in several connected action txs stored on EOS blockchain and it rebuild the file starting from the first one. 38 |

39 | 40 |

How many EOS do I need to upload XX kb?

41 |

42 | EOSfilestore has been tested mainly for small (under 1Mb files). NET quantity is predictable per transaction, but CPU is variable. You can test with a small file and then do a estimate with https://www.eosrp.io/#calc . 43 |

44 | 45 |

Are the files uploaded by EOSfilestore encrypted?

46 |

47 | No. EOS is a public blockchain so also the file content is readable by anyone. You can encrypt the file before the upload. 48 |

49 | 50 |
51 |
52 |
53 | 54 | 55 | ) 56 | } 57 | } 58 | 59 | export default InfoPage -------------------------------------------------------------------------------- /src/containers/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 67 | 📜 81 | 🔗 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/stores/file.tsx: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx' 2 | import { fetchTx, estimateBlob } from '../eosfilestore/core' 3 | import { notificationStore } from '.'; 4 | 5 | import { doTx } from '../eosfilestore/core' 6 | import { userStore } from '../stores'; 7 | import { splitString, createPayload } from '../eosfilestore/utils'; 8 | 9 | // import { counterStore, routingStore } from '.' 10 | 11 | // interface 12 | 13 | class FileStore { 14 | @observable transactions: any = [] 15 | @observable newTxid = '' 16 | @observable blob = '' 17 | @observable fileMetadata = { 18 | block_time: '...', 19 | cpu_usage_us: '...', 20 | net_usage_words: '...', 21 | num_txs: '...', 22 | upload_by: '...', 23 | } 24 | @observable isLoading = false 25 | @observable counter = Number(location.href.split(/\//)[4]) || 1 26 | @observable isOk = true 27 | @observable isErrorState = false 28 | 29 | @action setTxid(txid: string) { 30 | this.newTxid = txid 31 | // async start here 32 | } 33 | 34 | @action setBlob(blob: string, estimate?: boolean) { 35 | this.blob = blob 36 | const estData = estimateBlob(blob) 37 | if (estimate === true) { 38 | this.fileMetadata = { 39 | block_time: 'n/a', 40 | cpu_usage_us: estData.cpu_usage_us, 41 | net_usage_words: estData.net_usage_words, 42 | num_txs: estData.num_txs, 43 | upload_by: '...' 44 | } 45 | } 46 | // async start here 47 | } 48 | 49 | // NOTE: Upload should spin load until all tx uploaded 50 | @action async uploadFile() { 51 | const fileB64 = this.blob 52 | const chunks = splitString(fileB64) 53 | let nextTx: string | null = null 54 | let counter = 0 55 | 56 | notificationStore.push({ title: 'Scatter is opening..', message: 'You need to confirm all the windows to successfully upload the file.' }) 57 | 58 | for (const chunk of chunks.reverse()) { 59 | 60 | try { 61 | const memo = createPayload(chunk, nextTx) 62 | const doneTx: any = await doTx(memo, userStore.account) 63 | const cpu = doneTx.processed.receipt.cpu_usage_us 64 | const net = doneTx.processed.receipt.net_usage_words 65 | 66 | // NOTE: needed for better cli formatting 67 | console.log(`${counter}. ${nextTx} 68 | ${doneTx.transaction_id} cpu: ${cpu} net:${net}`) 69 | nextTx = doneTx.transaction_id 70 | counter += 1 71 | } catch (err) { 72 | console.log(`eosfilestore ERROR: ${JSON.stringify(err, null, 2)}`) 73 | return 74 | } 75 | 76 | } 77 | if (nextTx) { 78 | fileStore.setTxid(nextTx) 79 | } 80 | console.log(`Done, uploaded in ${nextTx}`) 81 | // async start here 82 | } 83 | 84 | @action fetchData() { 85 | this.isLoading = true 86 | this.isErrorState = false 87 | this.blob = '' 88 | const context = this 89 | function callback(tx: any) { 90 | context.transactions.push(tx) 91 | } 92 | fetchTx(this.newTxid, callback).then(({ data, fileMetadata }) => { 93 | console.log('ddd', data) 94 | this.blob = data.slice(9) 95 | this.isLoading = false 96 | this.fileMetadata = fileMetadata 97 | console.log('f', fileMetadata) 98 | // window.open(data.slice(9,)) 99 | // return false 100 | // const red = Base64.toByteArray(data) 101 | // window.open(data.slice(9,)) 102 | // console.log(red) 103 | // exists() 104 | }).catch(e => { 105 | this.isLoading = false 106 | this.isErrorState = true 107 | notificationStore.push({ message: 'Error fetching tx via API' }) 108 | console.error('ERROR: ', e) 109 | }) 110 | } 111 | 112 | @action cleanTxid() { 113 | this.newTxid = '' 114 | this.blob = '' 115 | } 116 | 117 | @action reset() { 118 | this.blob = '' 119 | this.newTxid = '' 120 | this.transactions = [] 121 | } 122 | 123 | } 124 | export const fileStore = new FileStore() -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-console 2 | // In production, we register a service worker to serve assets from local cache. 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 the 'N+1' visit to a page, since previously 7 | // cached resources are updated in the background. 8 | 9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 10 | // This link also includes instructions on opting out of this behavior. 11 | 12 | const isLocalhost = Boolean( 13 | window.location.hostname === 'localhost' || 14 | // [::1] is the IPv6 localhost address. 15 | window.location.hostname === '[::1]' || 16 | // 127.0.0.1/8 is considered localhost for IPv4. 17 | window.location.hostname.match( 18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 19 | ) 20 | ); 21 | 22 | export default function register() { 23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 24 | // The URL constructor is available in all browsers that support SW. 25 | const publicUrl = new URL( 26 | process.env.PUBLIC_URL!, 27 | window.location.toString() 28 | ); 29 | if (publicUrl.origin !== window.location.origin) { 30 | // Our service worker won't work if PUBLIC_URL is on a different origin 31 | // from what our page is served on. This might happen if a CDN is used to 32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 33 | return; 34 | } 35 | 36 | window.addEventListener('load', () => { 37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 38 | 39 | if (isLocalhost) { 40 | // This is running on localhost. Lets check if a service worker still exists or not. 41 | checkValidServiceWorker(swUrl); 42 | 43 | // Add some additional logging to localhost, pointing developers to the 44 | // service worker/PWA documentation. 45 | navigator.serviceWorker.ready.then(() => { 46 | console.log( 47 | 'This web app is being served cache-first by a service ' + 48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 49 | ); 50 | }); 51 | } else { 52 | // Is not local host. Just register service worker 53 | registerValidSW(swUrl); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | function registerValidSW(swUrl: string) { 60 | navigator.serviceWorker 61 | .register(swUrl) 62 | .then(registration => { 63 | registration.onupdatefound = () => { 64 | const installingWorker = registration.installing; 65 | if (installingWorker) { 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the old content will have been purged and 70 | // the fresh content will have been added to the cache. 71 | // It's the perfect time to display a 'New content is 72 | // available; please refresh.' message in your web app. 73 | console.log('New content is available; please refresh.'); 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // 'Content is cached for offline use.' message. 78 | console.log('Content is cached for offline use.'); 79 | } 80 | } 81 | }; 82 | } 83 | }; 84 | }) 85 | .catch(error => { 86 | console.error('Error during service worker registration:', error); 87 | }); 88 | } 89 | 90 | function checkValidServiceWorker(swUrl: string) { 91 | // Check if the service worker can be found. If it can't reload the page. 92 | fetch(swUrl) 93 | .then(response => { 94 | // Ensure service worker exists, and that we really are getting a JS file. 95 | if ( 96 | response.status === 404 || 97 | response.headers.get('content-type')!.indexOf('javascript') === -1 98 | ) { 99 | // No service worker found. Probably a different app. Reload the page. 100 | navigator.serviceWorker.ready.then(registration => { 101 | registration.unregister().then(() => { 102 | window.location.reload(); 103 | }); 104 | }); 105 | } else { 106 | // Service worker found. Proceed as normal. 107 | registerValidSW(swUrl); 108 | } 109 | }) 110 | .catch(() => { 111 | console.log( 112 | 'No internet connection found. App is running in offline mode.' 113 | ); 114 | }); 115 | } 116 | 117 | export function unregister() { 118 | if ('serviceWorker' in navigator) { 119 | navigator.serviceWorker.ready.then(registration => { 120 | registration.unregister(); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/containers/eosfilestore.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 18 | 22 | 26 | 27 | 29 | 33 | 37 | 38 | 45 | 48 | 49 | 56 | 59 | 60 | 68 | 77 | 78 | 80 | 81 | 83 | image/svg+xml 84 | 86 | 87 | 88 | 89 | 90 | 93 | 101 | eosfilestore 117 | 127 | 📜 141 | 🔗 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/containers/UploadPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import * as ReactDOM from 'react-dom'; 3 | import { Icon, FileInput, Callout, Intent, Button } from "@blueprintjs/core"; 4 | import { observer, inject } from 'mobx-react'; 5 | import { fileStore } from '../stores'; 6 | import { 7 | arrayBufferToBase64, 8 | // splitString, 9 | // createPayload 10 | } from '../eosfilestore/utils' 11 | // import { doTx } from '../eosfilestore/core' 12 | // import { userStore } from '../stores'; 13 | 14 | // import { counterStore, routingStore } from '../stores' 15 | 16 | // gif b5407140a0f7b6365b2dcf1731f6ad50ee0502d052ecdb9da01700ecd398f759 17 | // a jpg 90edc50fac5a622820404e24f6839c7b9ca2bff73a3b1a11221caf85334aa6e6 18 | // sh 987c4c9995e717df08ce82f4b24a2f814749f92113e7458124f29607cad0b77d 19 | // welcome txt 9a9e4d3637cbecea36d7cf54d0cf8a7e8046f0b893a1d880800ec8312c7d9eb4 20 | @inject('userStore') 21 | @observer 22 | class UploadPage extends React.Component{ 23 | componentDidMount() { 24 | // just in case it comes from DownloadPage 25 | fileStore.reset() 26 | } 27 | 28 | render() { 29 | 30 | 31 | 32 | return ( 33 | <> 34 |
35 |
36 |
37 |

38 | Select the file to upload 39 |

40 |

41 | Please read Info & FAQ before the usage, and try it first with a small file of a few Kb. 42 |

43 | { 47 | // const type = event.target.files[0].type 48 | 49 | 50 | 51 | const file = event.target.files[0]; 52 | console.log(file, event) 53 | const fr = new FileReader(); 54 | // NOTE: data is ProgressEvent but has problems with typescript 55 | fr.onloadend = async (data: any) => { 56 | if (data.target) { 57 | const fileB64 = arrayBufferToBase64(data.target.result, file.type) 58 | // senza type 59 | console.log(fileB64) // split and send 60 | fileStore.setBlob(fileB64, true) 61 | 62 | // bb 63 | // const chunks = splitString(fileB64, 1950) 64 | // let nextTx: string | null = null 65 | // let counter = 0 66 | // for (const chunk of chunks.reverse()) { 67 | 68 | // try { 69 | // const memo = createPayload(chunk, nextTx) 70 | // const doneTx: any = await doTx(memo, userStore.account) 71 | // const cpu = doneTx.processed.receipt.cpu_usage_us 72 | // const net = doneTx.processed.receipt.net_usage_words 73 | 74 | // // NOTE: needed for better cli formatting 75 | // console.log(`${counter}. ${nextTx} 76 | // ${doneTx.transaction_id} cpu: ${cpu} net:${net}`) 77 | // nextTx = doneTx.transaction_id 78 | // counter += 1 79 | // } catch (err) { 80 | // console.log(`eosfilestore ERROR: ${JSON.stringify(err, null, 2)}`) 81 | // return 82 | // } 83 | 84 | // } 85 | // if (nextTx) { 86 | // fileStore.setTxid(nextTx) 87 | // } 88 | // console.log(`Done, uploaded in ${nextTx}`) 89 | 90 | // bb 91 | 92 | } 93 | // debugger 94 | // if (data.target) { 95 | // console.log('dd', 96 | // // fr.readAsDataURL(data.target.result) 97 | // ) 98 | // } 99 | } 100 | fr.readAsArrayBuffer(file); 101 | }} /> 102 | {fileStore.newTxid ? ( 103 | 108 | Save the `txid` below as receipt proof or to download the file again
109 | {fileStore.newTxid} 110 |
111 | ) : null} 112 |
113 |
114 |
115 | 116 |
117 |
118 | {fileStore.blob.length > 0 ? ( 119 |
120 |
121 |
122 |

File to upload

123 | 124 |
125 |
126 | Upload date: {fileStore.fileMetadata.block_time}
127 | Account: {fileStore.fileMetadata.upload_by}
128 | Num txs: {fileStore.fileMetadata.num_txs}
129 | Total CPU: {fileStore.fileMetadata.cpu_usage_us}
130 | Total NET: {fileStore.fileMetadata.net_usage_words}
131 | Share direct link 132 |
133 | 134 |
135 |
145 | 146 |
147 |
148 | ) : null} 149 | 150 |
151 |
152 | 153 | ) 154 | } 155 | 156 | 157 | } 158 | 159 | export default UploadPage -------------------------------------------------------------------------------- /src/containers/DownloadPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // import * as ReactDOM from 'react-dom'; 3 | import { Button, InputGroup, ProgressBar, Intent, Icon } from "@blueprintjs/core"; 4 | import { observer } from 'mobx-react'; 5 | import { fileStore } from '../stores'; 6 | // import { counterStore, routingStore } from '../stores' 7 | 8 | // gif b5407140a0f7b6365b2dcf1731f6ad50ee0502d052ecdb9da01700ecd398f759 9 | // a jpg 90edc50fac5a622820404e24f6839c7b9ca2bff73a3b1a11221caf85334aa6e6 10 | // sh 987c4c9995e717df08ce82f4b24a2f814749f92113e7458124f29607cad0b77d 11 | // welcome txt 9a9e4d3637cbecea36d7cf54d0cf8a7e8046f0b893a1d880800ec8312c7d9eb4 12 | // HTML ef9445d69c8fe0e435ae01573422fc7aa2f62f81fe5e840c7ce90b131a027e13 13 | 14 | @observer 15 | class DownloadPage extends React.Component{ 16 | componentDidMount() { 17 | const txid = this.props.match.params.txid 18 | // NOTE: auto fetch if the url provide txid 19 | if (txid) { 20 | fileStore.setTxid(txid) 21 | fileStore.fetchData() 22 | } else { 23 | fileStore.reset() 24 | } 25 | } 26 | 27 | render() { 28 | 29 | const downloadButton: any = ( 30 |