├── dist ├── web │ └── .gitkeep └── electron │ └── .gitkeep ├── static ├── .gitkeep ├── icon.png └── badges │ ├── badge_1.png │ ├── badge_2.png │ ├── badge_3.png │ ├── badge_4.png │ ├── badge_5.png │ ├── badge_6.png │ ├── badge_7.png │ ├── badge_8.png │ ├── badge_9.png │ └── badge_9+.png ├── src ├── renderer │ ├── assets │ │ ├── .gitkeep │ │ ├── logo.png │ │ └── badges │ │ │ ├── badge.png │ │ │ ├── badge_1.png │ │ │ ├── badge_2.png │ │ │ ├── badge_3.png │ │ │ ├── badge_4.png │ │ │ ├── badge_5.png │ │ │ ├── badge_6.png │ │ │ ├── badge_7.png │ │ │ ├── badge_8.png │ │ │ ├── badge_9.png │ │ │ └── badge_9+.png │ ├── components │ │ ├── _message │ │ │ ├── index.js │ │ │ └── src │ │ │ │ ├── message.js │ │ │ │ └── message.vue │ │ ├── Root │ │ │ ├── chat.vue │ │ │ ├── search.vue │ │ │ ├── settings.vue │ │ │ ├── unlock.vue │ │ │ ├── users.vue │ │ │ └── account.vue │ │ ├── chat │ │ │ ├── user_header.vue │ │ │ ├── chat_history.vue │ │ │ └── text_entry.vue │ │ ├── Settings │ │ │ ├── blacklist.vue │ │ │ ├── nodes.vue │ │ │ ├── account │ │ │ │ ├── current_account.vue │ │ │ │ └── accounts.vue │ │ │ ├── settings.vue │ │ │ └── account.vue │ │ ├── _checkbox │ │ │ └── checkbox.vue │ │ ├── _modal │ │ │ └── modal.vue │ │ ├── intro │ │ │ └── intro.vue │ │ └── Root.vue │ ├── _g.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── index.js │ │ │ ├── general.js │ │ │ └── account.js │ ├── router │ │ └── index.js │ ├── db_user.js │ ├── db.js │ ├── main.js │ └── App.vue ├── index.ejs └── main │ ├── index.dev.js │ └── index.js ├── .snyk ├── .gitignore ├── .babelrc ├── appveyor.yml ├── .travis.yml ├── LICENSE ├── .electron-vue ├── dev-client.js ├── webpack.main.config.js ├── build.js ├── webpack.web.config.js ├── dev-runner.js └── webpack.renderer.config.js ├── README.md └── package.json /dist/web/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/icon.png -------------------------------------------------------------------------------- /src/renderer/components/_message/index.js: -------------------------------------------------------------------------------- 1 | import Message from './src/message.js' 2 | 3 | export default Message 4 | -------------------------------------------------------------------------------- /static/badges/badge_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_1.png -------------------------------------------------------------------------------- /static/badges/badge_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_2.png -------------------------------------------------------------------------------- /static/badges/badge_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_3.png -------------------------------------------------------------------------------- /static/badges/badge_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_4.png -------------------------------------------------------------------------------- /static/badges/badge_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_5.png -------------------------------------------------------------------------------- /static/badges/badge_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_6.png -------------------------------------------------------------------------------- /static/badges/badge_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_7.png -------------------------------------------------------------------------------- /static/badges/badge_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_8.png -------------------------------------------------------------------------------- /static/badges/badge_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_9.png -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /static/badges/badge_9+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/static/badges/badge_9+.png -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.10.2 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_1.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_2.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_3.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_4.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_5.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_6.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_7.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_8.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_9.png -------------------------------------------------------------------------------- /src/renderer/assets/badges/badge_9+.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therealwolf42/steem-chat-wallet/HEAD/src/renderer/assets/badges/badge_9+.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | node_modules/ 7 | npm-debug.log 8 | npm-debug.log.* 9 | thumbs.db 10 | !.gitkeep 11 | db/** 12 | *.log -------------------------------------------------------------------------------- /src/renderer/_g.js: -------------------------------------------------------------------------------- 1 | export let wait_sec = async (sec) => { 2 | return await Promise.all([ 3 | timeout(sec * 1000) 4 | ]) 5 | } 6 | 7 | function timeout(ms) { 8 | return new Promise(resolve => setTimeout(resolve, ms)); 9 | } -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import modules from './modules' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | modules, 10 | strict: process.env.NODE_ENV !== 'production' 11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | export default new Router({ 7 | routes: [ 8 | { 9 | path: '/', 10 | name: 'root', 11 | component: require('@/components/Root').default 12 | }, 13 | { 14 | path: '*', 15 | redirect: '/' 16 | } 17 | ] 18 | }) 19 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The file enables `@/store/index.js` to import all vuex modules 3 | * in a one-shot manner. There should not be any reason to edit this file. 4 | */ 5 | 6 | const files = require.context('.', false, /\.js$/) 7 | const modules = {} 8 | 9 | files.keys().forEach(key => { 10 | if (key === './index.js') return 11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | ["env", { 7 | "targets": { "node": 7 } 8 | }], 9 | "stage-0" 10 | ] 11 | }, 12 | "renderer": { 13 | "presets": [ 14 | ["env", { 15 | "modules": false 16 | }], 17 | "stage-0" 18 | ] 19 | }, 20 | "web": { 21 | "presets": [ 22 | ["env", { 23 | "modules": false 24 | }], 25 | "stage-0" 26 | ] 27 | } 28 | }, 29 | "plugins": ["transform-runtime"] 30 | } 31 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | image: Visual Studio 2017 8 | platform: 9 | - x64 10 | 11 | cache: 12 | - node_modules 13 | - '%APPDATA%\npm-cache' 14 | - '%USERPROFILE%\.electron' 15 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 16 | 17 | init: 18 | - git config --global core.autocrlf input 19 | 20 | install: 21 | - ps: Install-Product node 8 x64 22 | - choco install yarn --ignore-dependencies 23 | - git reset --hard HEAD 24 | - yarn 25 | - node --version 26 | 27 | build_script: 28 | - yarn build 29 | 30 | test: off 31 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Steem Chat-Wallet - 1.1 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/renderer/components/Root/chat.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 46 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Set environment for development 11 | process.env.NODE_ENV = 'development' 12 | 13 | // Install `electron-debug` with `devtron` 14 | require('electron-debug')({ showDevTools: true }) 15 | 16 | // Install `vue-devtools` 17 | require('electron').app.on('ready', () => { 18 | let installExtension = require('electron-devtools-installer') 19 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 20 | .then(() => {}) 21 | .catch(err => { 22 | console.log('Unable to install `vue-devtools`: \n', err) 23 | }) 24 | }) 25 | 26 | // Require `main` process to boot app 27 | require('./index') 28 | -------------------------------------------------------------------------------- /src/renderer/db_user.js: -------------------------------------------------------------------------------- 1 | import lowdb from 'lowdb' 2 | const fs = require('fs') 3 | var dir = (name) => { 4 | let path = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share") 5 | path = path + '/steemmessenger' 6 | if(!fs.existsSync(path)) { 7 | fs.mkdirSync(path) 8 | } 9 | if (!fs.existsSync(path + '/db/users')){ 10 | fs.mkdirSync(path + '/db/users') 11 | } 12 | return `${path}/db/users/${name}.json` 13 | } 14 | 15 | import { remote } from 'electron' 16 | 17 | const FileSync = require('lowdb/adapters/FileSync') 18 | 19 | 20 | const db_user = (name) => { 21 | let adapter = new FileSync(dir(name)) 22 | let db = lowdb(adapter) 23 | db.defaults({ 24 | name, 25 | last_transfer_num: 0, 26 | memo_key: '', 27 | active_key: '', 28 | transfers: [], 29 | hidelist: [], 30 | global_last_transfer: 0, 31 | notifications: [] 32 | }).write() 33 | return db 34 | } 35 | 36 | export default db_user -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8.3 2 | sudo: required 3 | dist: trusty 4 | language: c 5 | matrix: 6 | include: 7 | - os: osx 8 | - os: linux 9 | env: CC=clang CXX=clang++ npm_config_clang=1 10 | compiler: clang 11 | cache: 12 | directories: 13 | - node_modules 14 | - "$HOME/.electron" 15 | - "$HOME/.cache" 16 | addons: 17 | apt: 18 | packages: 19 | - libgnome-keyring-dev 20 | - icnsutils 21 | before_install: 22 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 23 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 24 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 25 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 26 | install: 27 | - nvm install 7 28 | - curl -o- -L https://yarnpkg.com/install.sh | bash 29 | - source ~/.bashrc 30 | - npm install -g xvfb-maybe 31 | - yarn 32 | script: 33 | - yarn run build 34 | branches: 35 | only: 36 | - master 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 therealwolf 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 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 | -------------------------------------------------------------------------------- /src/renderer/store/modules/general.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import db from '../../db' 3 | 4 | const state = { 5 | showModal: false, 6 | agreedTerms: false, 7 | loading: true, 8 | newVersion: false 9 | } 10 | 11 | const actions = { 12 | unlock: async ({ commit, state, dispatch }, { key, key_auths }) => { 13 | } 14 | } 15 | 16 | const getters = { 17 | showModal: state => { 18 | return state.showModal 19 | }, 20 | agreedTerms: state => { 21 | return state.agreedTerms 22 | }, 23 | loading: state => { 24 | return state.loading 25 | }, 26 | newVersion: state => { 27 | return state.newVersion 28 | } 29 | } 30 | 31 | const mutations = { 32 | setShowModal: (state, showModal) => { 33 | Vue.set(state, 'showModal', showModal) 34 | }, 35 | setAgreedTerms: (state, agreedTerms) => { 36 | Vue.set(state, 'agreedTerms', agreedTerms) 37 | }, 38 | setAgreedTermsDB: (state, agreedTerms) => { 39 | db.set('agreedTerms', agreedTerms).write() 40 | }, 41 | setLoading: (state, loading) => { 42 | Vue.set(state, 'loading', loading) 43 | }, 44 | newVersion: (state, newVersion) => { 45 | Vue.set(state, 'newVersion', newVersion) 46 | } 47 | } 48 | 49 | export default { 50 | state, 51 | mutations, 52 | actions, 53 | getters 54 | } -------------------------------------------------------------------------------- /src/renderer/db.js: -------------------------------------------------------------------------------- 1 | import lowdb from 'lowdb' 2 | var fs = require('fs'); 3 | var dir = () => { 4 | let path = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share") 5 | path = path + '/steemmessenger' 6 | if(!fs.existsSync(path)) { 7 | fs.mkdirSync(path) 8 | } 9 | if (!fs.existsSync(path + '/db')){ 10 | fs.mkdirSync(path + '/db') 11 | } 12 | return path + '/db/global.json' 13 | } 14 | 15 | const FileSync = require('lowdb/adapters/FileSync') 16 | 17 | const adapter = new FileSync(dir()) 18 | const db = lowdb(adapter) 19 | db.defaults({ 20 | username: '', 21 | accounts:[], 22 | settings: { 23 | use_smartsteem_blacklist: true, 24 | default_currency: 'SBD', 25 | use_encrypt: true, 26 | decrypt: true, 27 | default_amount: 0.001, 28 | interval: 10, 29 | timeout: 60, 30 | using_messenger: true, 31 | min_visible:0.001, 32 | min_alert: 0.002, 33 | enabled_alert: false, 34 | show_public: false 35 | }, 36 | agreedTerms:false, 37 | blacklist: [], 38 | smartsteem_blacklist: [], 39 | current_node: 'https://rpc.buildteam.io', 40 | version: '0.1.1' 41 | }).write() 42 | 43 | //db.get('transfers').reverse('time').value() 44 | 45 | 46 | //db.set('user.memo', 'K5asdadsd2d') 47 | // .write() 48 | 49 | export default db -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | * 7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/renderer/components/chat/user_header.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 46 | 47 | 76 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/blacklist.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 49 | 50 | 72 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js') 14 | }, 15 | externals: [ 16 | ...Object.keys(dependencies || {}) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | use: 'babel-loader', 23 | exclude: /node_modules/ 24 | }, 25 | { 26 | test: /\.node$/, 27 | use: 'node-loader' 28 | } 29 | ] 30 | }, 31 | node: { 32 | __dirname: process.env.NODE_ENV !== 'production', 33 | __filename: process.env.NODE_ENV !== 'production' 34 | }, 35 | output: { 36 | filename: '[name].js', 37 | libraryTarget: 'commonjs2', 38 | path: path.join(__dirname, '../dist/electron') 39 | }, 40 | plugins: [ 41 | new webpack.NoEmitOnErrorsPlugin() 42 | ], 43 | resolve: { 44 | extensions: ['.js', '.json', '.node'] 45 | }, 46 | target: 'electron-main' 47 | } 48 | 49 | /** 50 | * Adjust mainConfig for development settings 51 | */ 52 | if (process.env.NODE_ENV !== 'production') { 53 | mainConfig.plugins.push( 54 | new webpack.DefinePlugin({ 55 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 56 | }) 57 | ) 58 | } 59 | 60 | /** 61 | * Adjust mainConfig for production settings 62 | */ 63 | if (process.env.NODE_ENV === 'production') { 64 | mainConfig.plugins.push( 65 | new BabiliWebpackPlugin(), 66 | new webpack.DefinePlugin({ 67 | 'process.env.NODE_ENV': '"production"' 68 | }) 69 | ) 70 | } 71 | 72 | module.exports = mainConfig 73 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as axios from 'axios' 3 | import VTooltip from 'v-tooltip' 4 | import * as notifier from 'node-notifier' 5 | 6 | import Message from './components/_message' 7 | import Checkbox from './components/_checkbox/checkbox' 8 | 9 | import App from './App' 10 | import router from './router' 11 | import store from './store' 12 | import db from './db' 13 | import db_user from './db_user' 14 | import Modal from './components/_modal/modal' 15 | 16 | import { remote } from 'electron' 17 | 18 | remote.globalShortcut.register('CommandOrControl+Shift+K', () => { 19 | remote.BrowserWindow.getFocusedWindow().webContents.openDevTools() 20 | }) 21 | 22 | window.addEventListener('beforeunload', () => { 23 | remote.globalShortcut.unregisterAll() 24 | }) 25 | 26 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 27 | Vue.http = Vue.prototype.$http = axios 28 | Vue.config.productionTip = false 29 | 30 | Vue.prototype.$db = db 31 | Vue.prototype.$db_user = db_user 32 | Vue.prototype.$notifier = notifier 33 | 34 | Vue.directive('autoresize', { 35 | inserted: function (el) { 36 | let height = el.scrollHeight > 90 ? el.scrollHeight : 90 37 | el.style.height = height + 'px' 38 | el.style.resize = 'none' 39 | function OnInput() { 40 | height = el.scrollHeight > 90 ? el.scrollHeight : 90 41 | if(this.scrollHeight > 90) { 42 | el.style.overflowY = 'scroll' 43 | } else { 44 | el.style.overflowY = 'hidden' 45 | } 46 | this.style.height = 'auto'; 47 | this.style.height = height + 'px'; 48 | this.scrollTop = this.scrollHeight; 49 | window.scrollTo(window.scrollLeft, (this.scrollTop + this.scrollHeight)); 50 | } 51 | el.addEventListener("input", OnInput, false); 52 | } 53 | }) 54 | 55 | Vue.component('Modal', Modal) 56 | Vue.component('Message', Message) 57 | Vue.component('Checkbox', Checkbox) 58 | Vue.use(VTooltip) 59 | Vue.prototype.$Message = Message 60 | Vue.prototype.$Modal = Modal 61 | /* eslint-disable no-new */ 62 | new Vue({ 63 | components: { App }, 64 | router, 65 | store, 66 | template: '', 67 | data: { 68 | showModal: false 69 | } 70 | }).$mount('#app') 71 | -------------------------------------------------------------------------------- /src/renderer/components/Root/search.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 62 | 63 | 78 | -------------------------------------------------------------------------------- /src/renderer/components/chat/chat_history.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 57 | 58 | 99 | -------------------------------------------------------------------------------- /src/renderer/components/_checkbox/checkbox.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | 107 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/nodes.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 58 | 59 | 84 | -------------------------------------------------------------------------------- /src/renderer/components/_message/src/message.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import MessageVue from './message.vue' 3 | 4 | const MessageConstructor = Vue.extend(MessageVue) 5 | 6 | const messageType = ['info', 'success', 'warning', 'error', 'loading'] 7 | const instances = [] 8 | let seed = 1 9 | let zindexSeed = 1010 10 | 11 | const Message = options => { 12 | if (Vue.prototype.$isServer) { 13 | console.log('returned') 14 | return 15 | } 16 | 17 | options = options || {} 18 | 19 | if (typeof options === 'string') { 20 | options = { 21 | message: options 22 | } 23 | } 24 | 25 | const customCloseFunc = options.onClose 26 | const id = `message_${seed++}` 27 | 28 | options.onClose = () => { 29 | Message.close(id, customCloseFunc) 30 | } 31 | 32 | const instance = new MessageConstructor({ 33 | data: options 34 | }) 35 | 36 | instance.id = id 37 | instance.vm = instance.$mount() 38 | document.body.appendChild(instance.vm.$el) 39 | instance.vm.visible = true 40 | instance.dom = instance.vm.$el 41 | instance.dom.style.zIndex = zindexSeed++ 42 | 43 | const offset = 0 44 | const len = instances.length 45 | let topDist = offset 46 | 47 | for (let i = 0; i < len; i++) { 48 | topDist += instances[i].$el.offsetHeight + 8 49 | } 50 | 51 | topDist += 8 52 | instance.top = topDist 53 | 54 | instances.push(instance) 55 | 56 | // 返回关闭方法,用于手动消除 57 | return function () { 58 | instance.vm.close(id) 59 | } 60 | } 61 | 62 | Message.close = (id, customCloseFunc) => { 63 | const len = instances.length 64 | let index, removedHeight 65 | 66 | for (let i = 0; i < len; i++) { 67 | if (id === instances[i].id) { 68 | if (typeof customCloseFunc === 'function') { 69 | customCloseFunc(instances[i]) 70 | } 71 | index = i 72 | removedHeight = instances[i].dom.offsetHeight 73 | instances.splice(i, 1) 74 | break 75 | } 76 | } 77 | 78 | if (len > 1) { 79 | for (let i = index; i < len - 1; i++) { 80 | instances[i].dom.style.top = `${parseInt(instances[i].dom.style.top) - removedHeight - 8}px` 81 | } 82 | } 83 | } 84 | 85 | Message.closeAll = () => { 86 | instances.forEach((elem, idx) => { 87 | elem.close() 88 | }) 89 | } 90 | 91 | messageType.forEach(type => { 92 | Message[type] = options => { 93 | if (typeof options === 'string') { 94 | options = { 95 | message: options 96 | } 97 | } 98 | options.type = type 99 | options.icon = options.icon 100 | return Message(options) 101 | } 102 | }) 103 | 104 | export default Message 105 | -------------------------------------------------------------------------------- /src/renderer/components/_modal/modal.vue: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, Menu } from 'electron' 2 | import path from 'path' 3 | /** 4 | * Set `__static` path to static files in production 5 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 6 | */ 7 | if (process.env.NODE_ENV !== 'development') { 8 | global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\') 9 | } 10 | 11 | let template = [{ 12 | label: "Application", 13 | submenu: [ 14 | { label: "About Application", selector: "orderFrontStandardAboutPanel:" }, 15 | { type: "separator" }, 16 | { label: "Quit", accelerator: "Command+Q", click: function() { app.quit(); }} 17 | ]}, { 18 | label: "Edit", 19 | submenu: [ 20 | { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, 21 | { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, 22 | { type: "separator" }, 23 | { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, 24 | { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, 25 | { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, 26 | { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" } 27 | ]} 28 | ] 29 | 30 | let mainWindow 31 | const winURL = process.env.NODE_ENV === 'development' 32 | ? `http://localhost:9080` 33 | : `file://${__dirname}/index.html` 34 | 35 | function createWindow () { 36 | /** 37 | * Initial window options 38 | */ 39 | mainWindow = new BrowserWindow({ 40 | height: 475, 41 | useContentSize: true, 42 | width: 800, 43 | 'minHeight': 380, 44 | 'minWidth': 770, 45 | show: false, 46 | icon: path.join(__dirname, '/build/icons/icon.png') 47 | }) 48 | 49 | if (process.platform === 'darwin') { 50 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 51 | } 52 | 53 | mainWindow.once('ready-to-show', () => { 54 | mainWindow.show() 55 | }) 56 | 57 | mainWindow.loadURL(winURL) 58 | //mainWindow.webContents.openDevTools() 59 | mainWindow.on('closed', () => { 60 | mainWindow = null 61 | }) 62 | } 63 | 64 | app.on('ready', createWindow) 65 | 66 | app.on('window-all-closed', () => { 67 | if (process.platform !== 'darwin') { 68 | app.quit() 69 | } 70 | }) 71 | 72 | app.on('activate', () => { 73 | if (mainWindow === null) { 74 | createWindow() 75 | } 76 | }) 77 | 78 | /** 79 | * Auto Updater 80 | * 81 | * Uncomment the following code below and install `electron-updater` to 82 | * support auto updating. Code Signing with a valid certificate is required. 83 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 84 | */ 85 | 86 | /* 87 | import { autoUpdater } from 'electron-updater' 88 | 89 | autoUpdater.on('update-downloaded', () => { 90 | autoUpdater.quitAndInstall() 91 | }) 92 | 93 | app.on('ready', () => { 94 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 95 | }) 96 | */ 97 | -------------------------------------------------------------------------------- /src/renderer/components/Root/settings.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 43 | 44 | 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Steem Chat-Wallet (Beta)

2 | 3 | > Disclaimer: Steem Chat-Wallet is currently in Beta. While careful development and testing have been done - bugs should be expected. 4 | @therealwolf on Steem with funding from smartsteem.com cannot be held liable for unknown issues, loss of funds (SBD & STEEM) or mistakes made by the user. 5 | 6 | ![Messenger_1.png](https://steemitimages.com/DQmQ6NPUPXeqtpjp6jQ3U38RBY1Eywn3hkyk8S8znTz8Ufs/Messenger_1.png) 7 | 8 | Steem Chat-Wallet is the easiest way to communicate with other Steem Users through the blockchain (on-chain) via transfers. 9 | 10 | These messages (transfers) can be sent in plain text or in encrypted format via memo-keys. 11 | 12 | The project is currently in Beta Status and available for Windows, Mac & Linux. 13 | 14 | Releases: https://github.com/therealwolf42/steemmessenger/releases 15 | 16 | Bug Reports: https://github.com/therealwolf42/steemmessenger/issues 17 | 18 | Source Code: https://github.com/therealwolf42/steemmessenger 19 | 20 |

Features

21 | 22 | - Customized chat design - based on the look and feel of Discord. 23 | 24 | - Messages (transfers) can be sent in plain text and encrypted format. 25 | 26 | - The needed keys (active & memo) can be imported directly into the messenger or derived from the password, which are then saved in an encrypted format (with an encryption password) locally on the computer. 27 | 28 | - Incoming Messages are automatically fetched every x minutes (default: 10 minutes - can be customized) and also decrypted (if messenger is unlocked - more about that below) 29 | 30 | - The Messenger has to be unlocked in order to send any messages / decrypt new messages and will automatically lock itself after a period. (default 60 minutes - can be customized) 31 | 32 | - Notifications for new messages (currently not working on Linux) 33 | 34 | - Multiple accounts supported 35 | 36 | - Steem users can be blacklisted to block transfer spammers - additionally, Smartsteem.com is providing a global-blacklist consisting of known spammers & scammers. 37 | 38 | - Settings can be configured dynamically 39 | 40 | --- 41 | 42 |

Preview: Sending Messages

43 | 44 | ![SteemMessenger_Action.gif](https://steemitimages.com/DQmUm6dz3vBtzMH4mADrUNnsnuZc74i4h5e5DhvvZzh8XED/SteemMessenger_Action.gif) 45 | 46 | As seen in the gif above - messaging is only possible when the wallet is unlocked and an active key imported. Encryption is only possible when the account's memo key has been imported as well. 47 | 48 | --- 49 | 50 |

Build Guide

51 | 52 | ``` 53 | # install dependencies 54 | yarn / npm i 55 | 56 | # serve with hot reload at localhost:9080 57 | yarn run dev / npm run dev 58 | 59 | # build for production 60 | yarn run build / npm run build 61 | 62 | Note: 63 | 64 | Building for the OS currently only works on the specific OS - for mac on mac, windows on windows and linux on linux. 65 | ``` 66 | 67 | --- 68 | 69 |

No Support & No Warranty

70 | 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. 71 | -------------------------------------------------------------------------------- /src/renderer/components/Root/unlock.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 83 | 84 | 112 | -------------------------------------------------------------------------------- /src/renderer/components/Root/users.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 63 | 64 | 115 | -------------------------------------------------------------------------------- /src/renderer/components/intro/intro.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 67 | 68 | 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "steem-chat-wallet", 3 | "version": "0.1.1", 4 | "author": "therealwolf", 5 | "description": "Light Wallet with a Chat-Interface for Steem", 6 | "license": "MIT", 7 | "main": "./dist/electron/main.js", 8 | "scripts": { 9 | "build": "node .electron-vue/build.js && electron-builder", 10 | "build:mac": "node .electron-vue/build.js && electron-builder -mwl --x64 --ia32", 11 | "build:linux": "node .electron-vue/build.js && electron-builder --linux", 12 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 13 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 14 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 15 | "dev": "node .electron-vue/dev-runner.js", 16 | "pack": "npm run pack:main && npm run pack:renderer", 17 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 18 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 19 | "postinstall": "" 20 | }, 21 | "build": { 22 | "productName": "Steem Chat-Wallet", 23 | "appId": "com.steem.chat-wallet", 24 | "directories": { 25 | "output": "build" 26 | }, 27 | "files": [ 28 | "dist/electron/**/*" 29 | ], 30 | "dmg": { 31 | "contents": [ 32 | { 33 | "x": 410, 34 | "y": 150, 35 | "type": "link", 36 | "path": "/Applications" 37 | }, 38 | { 39 | "x": 130, 40 | "y": 150, 41 | "type": "file" 42 | } 43 | ] 44 | }, 45 | "mac": { 46 | "icon": "build/icons/icon.icns" 47 | }, 48 | "win": { 49 | "icon": "build/icons/icon.ico" 50 | }, 51 | "linux": { 52 | "icon": "build/icons" 53 | } 54 | }, 55 | "dependencies": { 56 | "axios": "^0.16.1", 57 | "crypto-js": "^3.1.9-1", 58 | "dsteem": "^0.8.7", 59 | "lodash": "^4.17.5", 60 | "lowdb": "^1.0.0", 61 | "moment": "^2.22.0", 62 | "node-notifier": "^5.2.1", 63 | "steem": "^0.7.1", 64 | "v-tooltip": "^2.0.0-rc.31", 65 | "vue": "^2.3.3", 66 | "vue-context-menu": "^2.0.6", 67 | "vue-electron": "^1.0.6", 68 | "vue-router": "^2.5.3", 69 | "vuex": "^2.3.1" 70 | }, 71 | "devDependencies": { 72 | "babel-core": "^6.25.0", 73 | "babel-loader": "^7.1.1", 74 | "babel-plugin-transform-runtime": "^6.23.0", 75 | "babel-preset-env": "^1.6.0", 76 | "babel-preset-stage-0": "^6.24.1", 77 | "babel-register": "^6.24.1", 78 | "babili-webpack-plugin": "^0.1.2", 79 | "cfonts": "^1.1.3", 80 | "chalk": "^2.1.0", 81 | "copy-webpack-plugin": "^4.0.1", 82 | "cross-env": "^5.0.5", 83 | "css-loader": "^0.28.4", 84 | "del": "^3.0.0", 85 | "devtron": "^1.4.0", 86 | "electron": "^1.7.11", 87 | "electron-builder": "^19.42.0", 88 | "electron-debug": "^1.4.0", 89 | "electron-devtools-installer": "^2.2.0", 90 | "extract-text-webpack-plugin": "^3.0.0", 91 | "file-loader": "^0.11.2", 92 | "html-webpack-plugin": "^2.30.1", 93 | "multispinner": "^0.2.1", 94 | "node-loader": "^0.6.0", 95 | "style-loader": "^0.18.2", 96 | "url-loader": "^0.5.9", 97 | "vue-html-loader": "^1.2.4", 98 | "vue-loader": "^13.0.5", 99 | "vue-style-loader": "^3.0.1", 100 | "vue-template-compiler": "^2.4.2", 101 | "webpack": "^3.5.2", 102 | "webpack-dev-server": "^2.7.1", 103 | "webpack-hot-middleware": "^2.18.2" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | const webConfig = require('./webpack.web.config') 16 | 17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 20 | const isCI = process.env.CI || false 21 | 22 | if (process.env.BUILD_TARGET === 'clean') clean() 23 | else if (process.env.BUILD_TARGET === 'web') web() 24 | else build() 25 | 26 | function clean () { 27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 28 | console.log(`\n${doneLog}\n`) 29 | process.exit() 30 | } 31 | 32 | function build () { 33 | greeting() 34 | 35 | del.sync(['dist/electron/*', '!.gitkeep']) 36 | 37 | const tasks = ['main', 'renderer'] 38 | const m = new Multispinner(tasks, { 39 | preText: 'building', 40 | postText: 'process' 41 | }) 42 | 43 | let results = '' 44 | 45 | m.on('success', () => { 46 | process.stdout.write('\x1B[2J\x1B[0f') 47 | console.log(`\n\n${results}`) 48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 49 | process.exit() 50 | }) 51 | 52 | pack(mainConfig).then(result => { 53 | results += result + '\n\n' 54 | m.success('main') 55 | }).catch(err => { 56 | m.error('main') 57 | console.log(`\n ${errorLog}failed to build main process`) 58 | console.error(`\n${err}\n`) 59 | process.exit(1) 60 | }) 61 | 62 | pack(rendererConfig).then(result => { 63 | results += result + '\n\n' 64 | m.success('renderer') 65 | }).catch(err => { 66 | m.error('renderer') 67 | console.log(`\n ${errorLog}failed to build renderer process`) 68 | console.error(`\n${err}\n`) 69 | process.exit(1) 70 | }) 71 | } 72 | 73 | function pack (config) { 74 | return new Promise((resolve, reject) => { 75 | webpack(config, (err, stats) => { 76 | if (err) reject(err.stack || err) 77 | else if (stats.hasErrors()) { 78 | let err = '' 79 | 80 | stats.toString({ 81 | chunks: false, 82 | colors: true 83 | }) 84 | .split(/\r?\n/) 85 | .forEach(line => { 86 | err += ` ${line}\n` 87 | }) 88 | 89 | reject(err) 90 | } else { 91 | resolve(stats.toString({ 92 | chunks: false, 93 | colors: true 94 | })) 95 | } 96 | }) 97 | }) 98 | } 99 | 100 | function web () { 101 | del.sync(['dist/web/*', '!.gitkeep']) 102 | webpack(webConfig, (err, stats) => { 103 | if (err || stats.hasErrors()) console.log(err) 104 | 105 | console.log(stats.toString({ 106 | chunks: false, 107 | colors: true 108 | })) 109 | 110 | process.exit() 111 | }) 112 | } 113 | 114 | function greeting () { 115 | const cols = process.stdout.columns 116 | let text = '' 117 | 118 | if (cols > 85) text = 'lets-build' 119 | else if (cols > 60) text = 'lets-|build' 120 | else text = false 121 | 122 | if (text && !isCI) { 123 | say(text, { 124 | colors: ['yellow'], 125 | font: 'simple3d', 126 | space: false 127 | }) 128 | } else console.log(chalk.yellow.bold('\n lets-build')) 129 | console.log() 130 | } 131 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | 141 | -------------------------------------------------------------------------------- /.electron-vue/webpack.web.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'web' 4 | 5 | const path = require('path') 6 | const webpack = require('webpack') 7 | 8 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | 13 | let webConfig = { 14 | devtool: '#cheap-module-eval-source-map', 15 | entry: { 16 | web: path.join(__dirname, '../src/renderer/main.js') 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.css$/, 22 | use: ExtractTextPlugin.extract({ 23 | fallback: 'style-loader', 24 | use: 'css-loader' 25 | }) 26 | }, 27 | { 28 | test: /\.html$/, 29 | use: 'vue-html-loader' 30 | }, 31 | { 32 | test: /\.js$/, 33 | use: 'babel-loader', 34 | include: [ path.resolve(__dirname, '../src/renderer') ], 35 | exclude: /node_modules/ 36 | }, 37 | { 38 | test: /\.vue$/, 39 | use: { 40 | loader: 'vue-loader', 41 | options: { 42 | extractCSS: true, 43 | loaders: { 44 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 45 | scss: 'vue-style-loader!css-loader!sass-loader' 46 | } 47 | } 48 | } 49 | }, 50 | { 51 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 52 | use: { 53 | loader: 'url-loader', 54 | query: { 55 | limit: 10000, 56 | name: 'imgs/[name].[ext]' 57 | } 58 | } 59 | }, 60 | { 61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 62 | use: { 63 | loader: 'url-loader', 64 | query: { 65 | limit: 10000, 66 | name: 'fonts/[name].[ext]' 67 | } 68 | } 69 | } 70 | ] 71 | }, 72 | plugins: [ 73 | new ExtractTextPlugin('styles.css'), 74 | new HtmlWebpackPlugin({ 75 | filename: 'index.html', 76 | template: path.resolve(__dirname, '../src/index.ejs'), 77 | minify: { 78 | collapseWhitespace: true, 79 | removeAttributeQuotes: true, 80 | removeComments: true 81 | }, 82 | nodeModules: false 83 | }), 84 | new webpack.DefinePlugin({ 85 | 'process.env.IS_WEB': 'true' 86 | }), 87 | new webpack.HotModuleReplacementPlugin(), 88 | new webpack.NoEmitOnErrorsPlugin() 89 | ], 90 | output: { 91 | filename: '[name].js', 92 | path: path.join(__dirname, '../dist/web') 93 | }, 94 | resolve: { 95 | alias: { 96 | '@': path.join(__dirname, '../src/renderer'), 97 | 'vue$': 'vue/dist/vue.esm.js' 98 | }, 99 | extensions: ['.js', '.vue', '.json', '.css'] 100 | }, 101 | target: 'web' 102 | } 103 | 104 | /** 105 | * Adjust webConfig for production settings 106 | */ 107 | if (process.env.NODE_ENV === 'production') { 108 | webConfig.devtool = '' 109 | 110 | webConfig.plugins.push( 111 | new BabiliWebpackPlugin(), 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.join(__dirname, '../static'), 115 | to: path.join(__dirname, '../dist/web/static'), 116 | ignore: ['.*'] 117 | } 118 | ]), 119 | new webpack.DefinePlugin({ 120 | 'process.env.NODE_ENV': '"production"' 121 | }), 122 | new webpack.LoaderOptionsPlugin({ 123 | minimize: true 124 | }) 125 | ) 126 | } 127 | 128 | module.exports = webConfig 129 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/account/current_account.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /src/renderer/components/_message/src/message.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 83 | 84 | 151 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/account/accounts.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 59 | 60 | 92 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats (proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer () { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.plugin('compilation', compilation => { 52 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.plugin('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | before (app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain () { 81 | return new Promise((resolve, reject) => { 82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) 83 | 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.plugin('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron () { 117 | electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')]) 118 | 119 | electronProcess.stdout.on('data', data => { 120 | electronLog(data, 'blue') 121 | }) 122 | electronProcess.stderr.on('data', data => { 123 | electronLog(data, 'red') 124 | }) 125 | 126 | electronProcess.on('close', () => { 127 | if (!manualRestart) process.exit() 128 | }) 129 | } 130 | 131 | function electronLog (data, color) { 132 | let log = '' 133 | data = data.toString().split(/\r?\n/) 134 | data.forEach(line => { 135 | log += ` ${line}\n` 136 | }) 137 | if (/[0-9A-z]+/.test(log)) { 138 | console.log( 139 | chalk[color].bold('┏ Electron -------------------') + 140 | '\n\n' + 141 | log + 142 | chalk[color].bold('┗ ----------------------------') + 143 | '\n' 144 | ) 145 | } 146 | } 147 | 148 | function greeting () { 149 | const cols = process.stdout.columns 150 | let text = '' 151 | 152 | if (cols > 104) text = 'electron-vue' 153 | else if (cols > 76) text = 'electron-|vue' 154 | else text = false 155 | 156 | if (text) { 157 | say(text, { 158 | colors: ['yellow'], 159 | font: 'simple3d', 160 | space: false 161 | }) 162 | } else console.log(chalk.yellow.bold('\n electron-vue')) 163 | console.log(chalk.blue(' getting ready...') + '\n') 164 | } 165 | 166 | function init () { 167 | greeting() 168 | 169 | Promise.all([startRenderer(), startMain()]) 170 | .then(() => { 171 | startElectron() 172 | }) 173 | .catch(err => { 174 | console.error(err) 175 | }) 176 | } 177 | 178 | init() 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | 14 | /** 15 | * List of node_modules to include in webpack bundle 16 | * 17 | * Required for specific packages like Vue UI libraries 18 | * that provide pure *.vue files that need compiling 19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 20 | */ 21 | let whiteListedModules = ['vue'] 22 | 23 | let rendererConfig = { 24 | devtool: '#cheap-module-eval-source-map', 25 | entry: { 26 | renderer: path.join(__dirname, '../src/renderer/main.js') 27 | }, 28 | externals: [ 29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.css$/, 35 | use: ExtractTextPlugin.extract({ 36 | fallback: 'style-loader', 37 | use: 'css-loader' 38 | }) 39 | }, 40 | { 41 | test: /\.html$/, 42 | use: 'vue-html-loader' 43 | }, 44 | { 45 | test: /\.js$/, 46 | use: 'babel-loader', 47 | exclude: /node_modules/ 48 | }, 49 | { 50 | test: /\.node$/, 51 | use: 'node-loader' 52 | }, 53 | { 54 | test: /\.vue$/, 55 | use: { 56 | loader: 'vue-loader', 57 | options: { 58 | extractCSS: process.env.NODE_ENV === 'production', 59 | loaders: { 60 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 61 | scss: 'vue-style-loader!css-loader!sass-loader' 62 | } 63 | } 64 | } 65 | }, 66 | { 67 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 68 | use: { 69 | loader: 'url-loader', 70 | query: { 71 | limit: 10000, 72 | name: 'imgs/[name]--[folder].[ext]' 73 | } 74 | } 75 | }, 76 | { 77 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 78 | loader: 'url-loader', 79 | options: { 80 | limit: 10000, 81 | name: 'media/[name]--[folder].[ext]' 82 | } 83 | }, 84 | { 85 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 86 | use: { 87 | loader: 'url-loader', 88 | query: { 89 | limit: 10000, 90 | name: 'fonts/[name]--[folder].[ext]' 91 | } 92 | } 93 | } 94 | ] 95 | }, 96 | node: { 97 | __dirname: process.env.NODE_ENV !== 'production', 98 | __filename: process.env.NODE_ENV !== 'production' 99 | }, 100 | plugins: [ 101 | new ExtractTextPlugin('styles.css'), 102 | new HtmlWebpackPlugin({ 103 | filename: 'index.html', 104 | template: path.resolve(__dirname, '../src/index.ejs'), 105 | minify: { 106 | collapseWhitespace: true, 107 | removeAttributeQuotes: true, 108 | removeComments: true 109 | }, 110 | nodeModules: process.env.NODE_ENV !== 'production' 111 | ? path.resolve(__dirname, '../node_modules') 112 | : false 113 | }), 114 | new webpack.HotModuleReplacementPlugin(), 115 | new webpack.NoEmitOnErrorsPlugin() 116 | ], 117 | output: { 118 | filename: '[name].js', 119 | libraryTarget: 'commonjs2', 120 | path: path.join(__dirname, '../dist/electron') 121 | }, 122 | resolve: { 123 | alias: { 124 | '@': path.join(__dirname, '../src/renderer'), 125 | 'vue$': 'vue/dist/vue.esm.js' 126 | }, 127 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 128 | }, 129 | target: 'electron-renderer' 130 | } 131 | 132 | /** 133 | * Adjust rendererConfig for development settings 134 | */ 135 | if (process.env.NODE_ENV !== 'production') { 136 | rendererConfig.plugins.push( 137 | new webpack.DefinePlugin({ 138 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 139 | }) 140 | ) 141 | } 142 | 143 | /** 144 | * Adjust rendererConfig for production settings 145 | */ 146 | if (process.env.NODE_ENV === 'production') { 147 | rendererConfig.devtool = '' 148 | 149 | rendererConfig.plugins.push( 150 | new BabiliWebpackPlugin(), 151 | new CopyWebpackPlugin([ 152 | { 153 | from: path.join(__dirname, '../static'), 154 | to: path.join(__dirname, '../dist/electron/static'), 155 | ignore: ['.*'] 156 | } 157 | ]), 158 | new webpack.DefinePlugin({ 159 | 'process.env.NODE_ENV': '"production"' 160 | }), 161 | new webpack.LoaderOptionsPlugin({ 162 | minimize: true 163 | }) 164 | ) 165 | } 166 | 167 | module.exports = rendererConfig 168 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/settings.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 154 | 155 | 187 | -------------------------------------------------------------------------------- /src/renderer/components/chat/text_entry.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 129 | 130 | 269 | -------------------------------------------------------------------------------- /src/renderer/components/Root.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 61 | 62 | 370 | -------------------------------------------------------------------------------- /src/renderer/components/Root/account.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 119 | 120 | 222 | -------------------------------------------------------------------------------- /src/renderer/components/Settings/account.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 284 | 285 | 365 | -------------------------------------------------------------------------------- /src/renderer/store/modules/account.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as _ from 'lodash' 3 | import { remote } from 'electron' 4 | import * as axios from 'axios' 5 | import * as notifier from 'node-notifier' 6 | 7 | import db from '../../db' 8 | import db_user from '../../db_user' 9 | import * as moment from 'moment' 10 | import Message from '../../components/_message' 11 | const fs = require('fs') 12 | import path from 'path' 13 | 14 | import { WSAETIMEDOUT } from 'constants' 15 | import { Store } from 'vuex' 16 | 17 | 18 | const { version } = require('../../../../package.json') 19 | 20 | var interval = null 21 | var interval_lock = null 22 | 23 | const steem = require('steem') 24 | const dsteem = require('dsteem') 25 | const nodes = ['https://rpc.buildteam.io', 'https://api.steemit.com', 'https://steemd.pevo.science', 'https://steemd.privex.io', 'https://gtg.steem.house:8090', 'https://steemd.minnowsupportproject.org', 'https://rpc.steemliberator.com', 'https://api.steem.house'] 26 | var current_node = nodes[0] 27 | 28 | var client = (node, timeout = 10) => { 29 | node = !node ? current_node : node 30 | return new dsteem.Client(node, { timeout: timeout * 1000 }) 31 | } 32 | 33 | var is_updating_transfers = false 34 | 35 | 36 | const state = { 37 | accounts: [], 38 | username: '', 39 | settings: { 40 | use_smartsteem_blacklist: true, 41 | default_currency: 'SBD', 42 | use_encrypt: true, 43 | decrypt: true, 44 | default_amount: 0.001, 45 | interval: 10, 46 | timeout: 60, 47 | using_messenger: true, 48 | min_visible:0.001, 49 | min_alert: 0.002, 50 | enabled_alert: false, 51 | show_public: true 52 | }, 53 | account: {}, 54 | transfers: [], 55 | users: [], 56 | active_key: '', 57 | active_key_decrypted: '', 58 | memo_key: '', 59 | memo_key_decrypted: '', 60 | selected_user: {}, 61 | blacklist: [], 62 | smartsteem_blacklist: [], 63 | hidelist: [], 64 | showSettings: false, 65 | current_node: nodes[0], 66 | nodes: nodes, 67 | unlocked: false, 68 | clicked_unlock: false, 69 | notifications: [], 70 | notifications_users: [], //main_user: STRING, count: INT 71 | global_last_transfer: 0, 72 | balance_steem: 0, 73 | balance_sbd: 0 74 | } 75 | 76 | const actions = { 77 | unlock: async ({ commit, state, dispatch }, { key, key_auths }) => { 78 | let account = await dispatch('getSteemAccount', state.username) 79 | let memo_valid = true 80 | let active_valid = true 81 | if (state.memo) memo_valid = await dispatch('testKey', { key: state.memo, key_auths: [[account.memo_key, 1]] }) 82 | 83 | if (this.active) active_valid = await dispatch('testKey', { key: this.active, key_auths: account.active.key_auths }) 84 | 85 | if (active_valid && memo_valid) { 86 | 87 | } 88 | }, 89 | testKey: async ({ commit, state }, { key, key_auths }) => { 90 | try { 91 | if (!key) return false 92 | 93 | let pub = steem.auth.wifToPublic(key) 94 | let filter = key_auths.filter(x => x[0] === pub) 95 | return filter.length > 0 96 | } catch (e) { 97 | console.error(e) 98 | this.clicked = false 99 | return false 100 | } 101 | }, 102 | deleteUserDB: async ({ commit, state, dispatch }, name) => { 103 | let path = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share") 104 | path = `${path}/steemmessenger/db/users/${name}.json` 105 | if (fs.existsSync(path)) fs.unlinkSync(path) 106 | 107 | }, 108 | initializeData: async ({ commit, state, dispatch }, payload) => { 109 | let global = db.value() 110 | let response = axios.get(`https://smartsteem.com/api/general/smartchat`) 111 | .then(response => { 112 | if (response) { 113 | let blacklist = response.data.blacklist 114 | let sm_version = response.data.version 115 | if (sm_version > version) commit('newVersion', true) 116 | commit('setSmartsteemBlacklist', blacklist) 117 | 118 | } 119 | }) 120 | 121 | if (version > global.version) { 122 | Object.keys(state.settings).forEach(state_setting => { 123 | let is_new = true 124 | for (let db_setting in global.settings) { 125 | if (state_setting === db_setting) { 126 | is_new = false 127 | break 128 | } 129 | } 130 | if (is_new) global.settings[state_setting] = state.settings[state_setting] 131 | }) 132 | commit('setVersion', version) 133 | } 134 | 135 | commit('setSettings', global.settings) 136 | commit('setBlacklist', global.blacklist) 137 | commit('setAgreedTerms', global.agreedTerms) 138 | commit('setCurrentNode', global.current_node) 139 | }, 140 | initializeUser: async ({ commit, state, dispatch }, { first = false }) => { 141 | let user = db_user(state.username).value() 142 | commit('setUser', user) 143 | let transfers = user.transfers 144 | if (!transfers || transfers.length <= 0) { 145 | commit('setLastTransferNum', 0) 146 | } else { 147 | commit('setTransfersDB', transfers) 148 | } 149 | if (transfers.length > 0) { 150 | if (state.settings.use_smartsteem_blacklist) transfers = transfers.filter(x => !state.smartsteem_blacklist.includes(x.main_user)) 151 | transfers = transfers.filter(x => !state.blacklist.includes(x.main_user)) 152 | } 153 | 154 | await commit('setTransfers', { transfers, updating: false }) 155 | await dispatch('updateTransfers', { first }) 156 | commit('updateSelectedUser', state.selected_user) 157 | commit('removeNotification', { main_user: state.selected_user.main_user }) 158 | commit('setBadge') 159 | 160 | clearInterval(interval) 161 | let interval_number = !state.settings.interval || state.settings.interval < 1 ? 2 : state.settings.interval 162 | 163 | interval = setInterval(async function () { 164 | if (!is_updating_transfers) { 165 | await dispatch('updateTransfers', { first }) 166 | commit('updateSelectedUser', state.selected_user) 167 | } 168 | }, 1000 * 60 * interval_number) 169 | 170 | }, 171 | changeUser: async ({ commit, state, dispatch }, { name }) => { 172 | try { 173 | commit('setUnlocked', false) 174 | commit('setClickedUnlock', false) 175 | if (name) { 176 | dispatch('initializeUser', { first: true }) 177 | } 178 | 179 | return true 180 | } catch (e) { 181 | Message.error(e.message) 182 | return false 183 | } 184 | }, 185 | transfer: async ({ commit, state }, payload) => { 186 | try { 187 | let { amount, asset, memo } = payload 188 | amount = dsteem.Asset.from(amount, asset) 189 | let from = state.username 190 | let to = state.selected_user.main_user 191 | let active_key = dsteem.PrivateKey.from(state.active_key_decrypted) 192 | await client(state.current_node, 60).broadcast.transfer({ from, to, amount, memo }, active_key) 193 | Message.success('Send Message') 194 | return true 195 | } catch (error) { 196 | Message.error(error.message) 197 | return false 198 | } 199 | }, 200 | decodeAllTransfers: async ({ commit, state, dispatch }, memo) => { 201 | let transfers = state.transfers 202 | if( !state.selected_user || !transfers || !state.selected_user.transfers ) return 203 | if (!state.selected_user || state.selected_user.transfers.length <= 0) return 204 | commit('decodeAllTransfers', transfers) 205 | commit('setTransfersDB', state.transfers) 206 | commit('setTransfers', { transfers: state.transfers, updating: false }) 207 | }, 208 | decode: async ({ commit, state, dispatch }, memo) => { 209 | //let memo_key = state.account.memo_key# 210 | try { 211 | if (!memo) return '' 212 | if (!state.memo_key_decrypted) return '' 213 | let decoded = memo.substring(0, 1) === '#' && memo.length > 80 && !memo.substring(0, 50).includes(' ') ? steem.memo.decode(state.memo_key_decrypted, memo) : '' 214 | return decoded 215 | } catch (e) { 216 | console.error(`Decoding`, e) 217 | return '' 218 | } 219 | }, 220 | getSteemAccount: async ({ commit, state, dispatch }, name) => { 221 | try { 222 | if (name.length < 3 && name.length > 0) { return false } 223 | let acc = await client(state.current_node).database.getAccounts([name]) 224 | acc = acc[0] 225 | if(acc && acc.name === state.username) commit('setBalances', { balance_steem: convert_float(acc.balance, 'STEEM'), balance_sbd: convert_float(acc.sbd_balance, 'SBD') }) 226 | return acc 227 | } catch (error) { 228 | console.error('getSteemAccount', error) 229 | return {} 230 | } 231 | }, 232 | updateAccount: async ({ commit, state, dispatch }, { account, memo_key, json_metadata }) => { 233 | try { 234 | let active_key = dsteem.PrivateKey.from(state.active_key_decrypted) 235 | await client(state.current_node).broadcast.updateAccount({ account, memo_key, json_metadata }, active_key) 236 | return true 237 | } catch (error) { 238 | console.error('updateAccount', error) 239 | return false 240 | } 241 | }, 242 | updateTransfers: async ({ commit, state, getters, dispatch }, { first, retries = 0, whole_history = false }) => { 243 | try { 244 | if (!state.username) return [] 245 | if (is_updating_transfers) return [] 246 | is_updating_transfers = true 247 | let transfers = [] 248 | let last_transfer_num = state.last_transfer_num | 0 249 | 250 | let transactions = [] 251 | 252 | // Recursive transfer fetching not yet working 253 | /*if (whole_history && (last_transfer_num > 0 && last_transfer_num > 10000)) { 254 | let fetched_all = false 255 | let start = last_transfer_num 256 | while (!fetched_all) { 257 | let new_trans = await client(state.current_node).database.call('get_account_history', [state.username, start, start >= 10000 ? 10000 : start]) 258 | transactions = transactions.concat(new_trans) 259 | start -= 10000 260 | if (start <= 0 || new_trans.length < 10000) { 261 | console.log('start at 0 or length under 10000') 262 | fetched_all = true 263 | break 264 | } 265 | 266 | } 267 | console.log(transactions) 268 | } else {*/ 269 | transactions = await client(state.current_node).database.call('get_account_history', [state.username, -1, first ? 10000 : 2000]) 270 | //} 271 | 272 | if (transactions.length <= 0) return state.transfers 273 | 274 | transactions = _.orderBy(transactions, [0], ['desc']) 275 | let notifications = state.notifications 276 | let notif_count = 0 277 | 278 | let last_num = transactions[transactions.length - 1][0] 279 | 280 | let win = remote.getCurrentWindow() 281 | let dock = remote.app.dock // Badge works only on Mac 282 | 283 | let window_visible = true 284 | if (process.platform === 'darwin') { 285 | window_visible = dock && win.isFocused() 286 | } else { 287 | window_visible = document.visibilityState !== 'hidden' 288 | } 289 | 290 | for await (const trx of transactions) { 291 | if (trx[0] <= last_transfer_num && !whole_history) { 292 | break 293 | } 294 | let type = trx[1].op[0] 295 | let data = trx[1].op[1] 296 | 297 | if (type === 'transfer' && (trx[0] > last_transfer_num || whole_history)) { 298 | if (state.transfers.filter(x => x.number === trx[0]).length > 0) continue 299 | 300 | let amount_arr = data.amount.split(" ") 301 | amount_arr[0] = Number(amount_arr[0]) 302 | 303 | let time_value = moment.utc(trx[1].timestamp).valueOf() 304 | let main_user = data.from === state.username ? data.to : data.from 305 | 306 | let decoded = await dispatch('decode', data.memo) 307 | let transfer = { number: trx[0], id: trx[1].trx_id, time: trx[1].timestamp, time_value, main_user, from: data.from, to: data.to, amount: amount_arr[0], asset: amount_arr[1], memo: data.memo, decoded } 308 | transfers.push(transfer) 309 | 310 | if (!first && data.from !== state.username 311 | && !(state.selected_user.main_user === data.from && window_visible) 312 | && state.blacklist.indexOf(data.from) === -1 313 | && state.smartsteem_blacklist.indexOf(data.from) === -1) { 314 | notif_count += 1 315 | //console.log(state.settings.enabled_alert, amount_arr[0] >= state.settings.min_alert) 316 | if(state.settings.enabled_alert && amount_arr[0] >= state.settings.min_alert) { 317 | notifier.notify({ 318 | title: `${data.amount} Message from ${data.from}`, 319 | message: decoded ? decoded : (data.memo ? data.memo : 'Empty Message'), 320 | icon: path.join(__static, 'icon.png'), // Absolute path (doesn't work on balloons) 321 | sound: false, // Only Notification Center or Windows Toasters 322 | wait: true // Wait with callback, until user action is taken against notification 323 | }) 324 | } 325 | commit('addNotification', { main_user: data.from }) 326 | } 327 | 328 | if (state.hidelist.indexOf(main_user) > -1) { 329 | commit('removeHidelistUser', main_user) 330 | } 331 | } 332 | } 333 | 334 | if (last_num > last_transfer_num) { 335 | last_transfer_num = last_num 336 | } else { 337 | last_transfer_num = transactions[0][0] 338 | } 339 | 340 | commit('addTransfers', transfers) 341 | commit('setTransfersDB', state.transfers) 342 | commit('setTransfers', { transfers: state.transfers, updating: true }) 343 | 344 | commit('setLastTransferNum', last_transfer_num) 345 | commit('setBadge') 346 | is_updating_transfers = false 347 | if (!first) Message.success('Updated Transfers') 348 | return state.transfers 349 | } catch (e) { 350 | Message.error(e.message) 351 | console.error(e) 352 | if (e.message.includes('timeout')) { 353 | retries += 1 354 | if (retries > 3) { 355 | is_updating_transfers = false 356 | return [] 357 | } 358 | Message.info(`Retrying to update transfers`) 359 | await dispatch('updateTransfers', { first, retries: retries }) 360 | } 361 | is_updating_transfers = false 362 | } 363 | } 364 | } 365 | 366 | const getters = { 367 | clickedUnlock: state => { 368 | return state.clicked_unlock 369 | }, 370 | Unlocked: state => { 371 | return state.unlocked 372 | }, 373 | Accounts: state => { 374 | return state.accounts 375 | }, 376 | User: state => { 377 | return state.user 378 | }, 379 | Username: state => { 380 | return state.username 381 | }, 382 | currentAccount: state => { 383 | return state.account 384 | }, 385 | currentTransfers: state => { 386 | return state.transfers 387 | }, 388 | users: state => { 389 | return state.users 390 | }, 391 | selectedUser: state => { 392 | return state.selected_user 393 | }, 394 | hidelist: state => { 395 | return state.hidelist 396 | }, 397 | blacklist: state => { 398 | return state.blacklist 399 | }, 400 | smartsteem_blacklist: state => { 401 | return state.smartsteem_blacklist 402 | }, 403 | settings: state => { 404 | return state.settings 405 | }, 406 | showSettings: state => { 407 | return state.showSettings 408 | }, 409 | memo_key: state => { 410 | return state.memo_key 411 | }, 412 | memo_key_decrypted: state => { 413 | return state.memo_key_decrypted 414 | }, 415 | active_key: state => { 416 | return state.active_key 417 | }, 418 | active_key_decrypted: state => { 419 | return state.active_key_decrypted 420 | }, 421 | currentNode: state => { 422 | return state.current_node 423 | }, 424 | nodes: state => { 425 | return state.nodes 426 | }, 427 | notifications: state => { 428 | return state.notifications 429 | }, 430 | global_last_transfer: state => { 431 | return state.global_last_transfer 432 | }, 433 | balanceSBD: state => { 434 | return state.balance_sbd 435 | }, 436 | balanceSTEEM: state => { 437 | return state.balance_steem 438 | } 439 | } 440 | 441 | const mutations = { 442 | setBalances: (state, { balance_sbd, balance_steem }) => { 443 | Vue.set(state, 'balance_steem', balance_steem) 444 | Vue.set(state, 'balance_sbd', balance_sbd) 445 | }, 446 | setVersion: (state, version) => { 447 | db.set('version', version).write() 448 | }, 449 | setGlobalLastTransfer: (state, last_transfer) => { 450 | Vue.set(state, 'global_last_transfer', last_transfer) 451 | }, 452 | setGlobalLastTransferDB: (state, last_transfer) => { 453 | db_user(state.username).set('global_last_transfer', last_transfer).write() 454 | }, 455 | setBadge: (state) => { 456 | let notifications = state.notifications 457 | if (process.platform === 'win32') { 458 | let win = remote.getCurrentWindow() 459 | if (notifications.length <= 0) { 460 | win.setOverlayIcon(null, '') 461 | } else { 462 | notifications = notifications.map(x => x.count).reduce((prev, next) => prev + next); 463 | if (notifications > 0) { 464 | let image_path = getImagePath(notifications) 465 | win.setOverlayIcon(image_path, `${notifications} Notifications`) 466 | } else { 467 | win.setOverlayIcon(null, '') 468 | } 469 | } 470 | } else { 471 | //let dock = remote.app.dock // Badge works only on Mac 472 | let app = remote.app 473 | if (!app) return 474 | if (notifications.length <= 0) { 475 | app.setBadgeCount(0) 476 | } else { 477 | notifications = notifications.map(x => x.count).reduce((prev, next) => prev + next); 478 | if (notifications <= 0) { 479 | app.setBadgeCount(0) 480 | } else { 481 | app.setBadgeCount(notifications) 482 | } 483 | } 484 | 485 | 486 | } 487 | }, 488 | addNotification: (state, { main_user }) => { 489 | let index = state.notifications.map(x => x.main_user).indexOf(main_user) //notifications.filter(x => x.main_user === main_user) 490 | if (index > -1) { 491 | state.notifications[index].count += 1 492 | } else { 493 | state.notifications.push({ main_user, count: 1 }) 494 | } 495 | Vue.set(state, 'notifications', state.notifications) 496 | db_user(state.username).set('notifications', state.notifications).write() 497 | }, 498 | removeNotification: (state, { main_user }) => { 499 | let index = state.notifications.map(x => x.main_user).indexOf(main_user) //notifications.filter(x => x.main_user === main_user) 500 | if (index > -1) { 501 | state.notifications.splice(index, 1) 502 | Vue.set(state, 'notifications', state.notifications) 503 | db_user(state.username).set('notifications', state.notifications).write() 504 | } 505 | }, 506 | setNotifications: (state, notifications) => { 507 | Vue.set(state, 'notifications', notifications) 508 | db_user(state.username).set('notifications', notifications).write() 509 | }, 510 | addAccount: (state, account) => { 511 | let found = false 512 | for (let i = 0; i < state.accounts.length; i++) { 513 | if (state.accounts[i].name === account.name) { 514 | state.accounts[i] = account 515 | found = true 516 | break 517 | } 518 | } 519 | 520 | if (!found) state.accounts.push(account) 521 | 522 | Vue.set(state, 'accounts', state.accounts) 523 | db.set('accounts', state.accounts).write() 524 | }, 525 | removeAccount: (state, name) => { 526 | if (state.username === name) clearInterval(interval) 527 | let accounts = state.accounts 528 | let index = -1 529 | for (let acc of accounts) { 530 | if (acc.name === name) index = accounts.indexOf(acc) 531 | } 532 | if (index > -1) accounts.splice(index, 1) 533 | Vue.set(state, 'accounts', accounts) 534 | db.set('accounts', accounts).write() 535 | }, 536 | setAccounts: (state, accounts) => { 537 | state.accounts.concat(accounts) 538 | Vue.set(state, 'accounts', accounts) 539 | }, 540 | setClickedUnlock: (state, clicked) => { 541 | if (clicked) clicked = !state.clicked_unlock 542 | Vue.set(state, 'clicked_unlock', clicked) 543 | }, 544 | setUnlocked: async (state, unlocked) => { 545 | Vue.set(state, 'unlocked', unlocked) 546 | }, 547 | decodeAllTransfers: (state, transfers) => { 548 | if (!state.memo_key_decrypted) return transfers 549 | for (let transfer of transfers) { 550 | try { 551 | if (!transfer.memo || transfer.decoded) continue 552 | let decoded = transfer.memo.substring(0, 1) === '#' && transfer.memo.length > 80 && !transfer.memo.substring(0, 50).includes(' ') ? steem.memo.decode(state.memo_key_decrypted, transfer.memo) : '' 553 | transfer.decoded = decoded 554 | } catch (e) { 555 | console.error(`Decoding`, e.message) 556 | console.log(transfer.memo) 557 | } 558 | } 559 | Vue.set(state, 'transfers', transfers) 560 | }, 561 | setUserName: (state, username) => { 562 | db.set('username', username).write() 563 | Vue.set(state, 'username', username) 564 | }, 565 | setCurrentNode: (state, node) => { 566 | Vue.set(state, 'current_node', node) 567 | }, 568 | setCurrentNodeDB: (state, node) => { 569 | db.set('current_node', node).write() 570 | }, 571 | setHidelist: (state, hidelist) => { 572 | Vue.set(state, 'hidelist', hidelist) 573 | }, 574 | setLastTransferNum: (state, last_transfer_num) => { 575 | db_user(state.username).set('last_transfer_num', last_transfer_num).write() 576 | Vue.set(state, 'last_transfer_num', last_transfer_num) 577 | }, 578 | setKeyDecrypted: (state, { key_type, key }) => { 579 | Vue.set(state, `${key_type}_decrypted`, key) 580 | }, 581 | setKey: (state, { key_type, key }) => { 582 | Vue.set(state, key_type, key) 583 | db_user(state.username).set(key_type, key).write() 584 | }, 585 | setShowSettings: (state, settings) => { 586 | Vue.set(state, 'showSettings', !state.showSettings) 587 | }, 588 | setSetting: (state, { key, value }) => { 589 | let settings = state.settings 590 | settings[key] = value 591 | Vue.set(state, 'settings', settings) 592 | db.set('settings', settings).write() 593 | }, 594 | updateSettings: (state, payload) => { 595 | let settings = state.settings 596 | Object.keys(payload).forEach(key => { 597 | settings[key] = payload[key] 598 | }) 599 | Vue.set(state, 'settings', settings) 600 | db.set('settings', settings).write() 601 | }, 602 | setSettings: (state, settings) => { 603 | Vue.set(state, 'settings', settings) 604 | db.set('settings', settings).write() 605 | }, 606 | hideUser: (state, main_user) => { 607 | let hidelist = db_user(state.username).get('hidelist').value() 608 | if (hidelist.indexOf(main_user) === -1) { 609 | hidelist.push(main_user) 610 | db_user(state.username).set('hidelist', hidelist).write() 611 | Vue.set(state, 'hidelist', hidelist) 612 | let users = state.users.filter(x => !state.hidelist.includes(x.main_user)) 613 | Vue.set(state, 'users', users) 614 | if (state.users.length > 1) { 615 | let new_selected_user = state.users[0].main_user === main_user ? state.users[1] : state.users[0] 616 | Vue.set(state, 'selected_user', new_selected_user) 617 | } else { 618 | Vue.set(state, 'selected_user', {}) 619 | } 620 | } 621 | }, 622 | ignoreUser: (state, main_user) => { 623 | let blacklist = db.get('blacklist').value() 624 | if (blacklist.indexOf(main_user) === -1) { 625 | blacklist.push(main_user) 626 | db.set('blacklist', blacklist).write() 627 | Vue.set(state, 'blacklist', blacklist) 628 | if (state.users.length > 1) { 629 | let users = state.users.filter(x => !state.blacklist.includes(x.main_user)) 630 | Vue.set(state, 'users', users) 631 | let new_selected_user = state.users[0].main_user === main_user ? state.users[1] : state.users[0] 632 | Vue.set(state, 'selected_user', new_selected_user) 633 | } else { 634 | Vue.set(state, 'selected_user', {}) 635 | } 636 | } 637 | }, 638 | removeHidelistUser: (state, user) => { 639 | let hidelist = state.hidelist 640 | let index = hidelist.indexOf(user) 641 | if (index > -1) { 642 | hidelist.splice(index, 1) 643 | Vue.set(state, 'hidelist', hidelist) 644 | db_user(state.username).set('hidelist', hidelist).write() 645 | } 646 | }, 647 | removeBlacklistUser: (state, user) => { 648 | let blacklist = state.blacklist 649 | let index = blacklist.indexOf(user) 650 | if (index > -1) { 651 | blacklist.splice(index, 1) 652 | Vue.set(state, 'blacklist', blacklist) 653 | } 654 | }, 655 | setBlacklistDB: (state, blacklist) => { 656 | blacklist = blacklist ? blacklist : state.blacklist 657 | db.set('blacklist', blacklist).write() 658 | }, 659 | setBlacklist: (state, blacklist) => { 660 | Vue.set(state, 'blacklist', blacklist) 661 | }, 662 | setSmartsteemBlacklist: (state, blacklist) => { 663 | Vue.set(state, 'smartsteem_blacklist', blacklist) 664 | }, 665 | resetTransfers: (state) => { 666 | db_user(state.username).set('transfers', []).write() 667 | Vue.set(state, 'transfers', []) 668 | db_user(state.username).set('last_transfer_num', 0).write() 669 | Vue.set(state, 'last_transfer_num', 0) 670 | db_user(state.username).set('user', user).write() 671 | Vue.set(state, 'user', user) 672 | }, 673 | resetUser: (state) => { 674 | Vue.set(state, 'username', '') 675 | Vue.set(state, 'last_transfer_num', 0) 676 | Vue.set(state, 'memo_key', '') 677 | Vue.set(state, 'memo_key_decrypted', '') 678 | Vue.set(state, 'active_key', '') 679 | Vue.set(state, 'active_key_decrypted', '') 680 | Vue.set(state, 'transfers', []) 681 | Vue.set(state, 'hidelist', []) 682 | Vue.set(state, 'users', []) 683 | Vue.set(state, 'selected_user', {}) 684 | }, 685 | updateSelectedUser: (state, selected_user) => { 686 | if (!selected_user) { 687 | selected_user = state.users.length > 0 ? state.users[0] : {} 688 | } else { 689 | selected_user = state.users.filter(x => x.main_user === selected_user.main_user)[0] 690 | } 691 | Vue.set(state, 'selected_user', selected_user) 692 | }, 693 | setSelectedUser: (state, selected_user) => { 694 | Vue.set(state, 'selected_user', selected_user) 695 | }, 696 | setUserDB: (state, user) => { 697 | db_user(state.username).set('user', user).write() 698 | }, 699 | setTransfersDB: (state, transfers) => { 700 | db_user(state.username).set('transfers', transfers).write() 701 | }, 702 | setUser: (state, { username, last_transfer_num, memo_key, active_key, transfers, hidelist, notifications }) => { 703 | Vue.set(state, 'last_transfer_num', last_transfer_num) 704 | Vue.set(state, 'memo_key', memo_key) 705 | Vue.set(state, 'active_key', active_key) 706 | Vue.set(state, 'transfers', transfers) 707 | Vue.set(state, 'hidelist', hidelist) 708 | Vue.set(state, 'notifications', notifications) 709 | }, 710 | setAccount: (state, { account }) => { 711 | Vue.set(state, 'account', account) 712 | }, 713 | addTransfers: (state, transfers) => { 714 | transfers = state.transfers.concat(transfers) 715 | Vue.set(state, 'transfers', transfers) 716 | }, 717 | setTransfers: async (state, { transfers, updating }) => { 718 | transfers = _.orderBy(transfers, ['time_value'], ['desc']) 719 | let users = _(transfers) 720 | .sortBy(['time_value'], ['asc']) 721 | .groupBy('main_user') 722 | .toPairs() 723 | .map((arr) => { 724 | let i = arr[1].length; return { 725 | main_user: arr[1][0]['main_user'], 726 | last_transfer: arr[1][i - 1]['time_value'], 727 | transfers: arr[1], 728 | notifications: filter_last_transfer(arr[1], arr[1][0]['main_user']) 729 | } 730 | }) 731 | .orderBy(['last_transfer'], ['desc']) 732 | .value() 733 | 734 | users = users.filter(x => !state.hidelist.includes(x.main_user)) 735 | if (state.settings.use_smartsteem_blacklist) users = users.filter(x => !state.smartsteem_blacklist.includes(x.main_user)) 736 | users = users.filter(x => !state.blacklist.includes(x.main_user)) 737 | Vue.set(state, 'users', users) 738 | let selected_user = state.selected_user 739 | if (!selected_user && users.length > 0) { 740 | selected_user = users[0] 741 | } else if (!selected_user.main_user && users.length > 0) { 742 | selected_user = users[0] 743 | } else { 744 | selected_user = users.filter(x => x.main_user === selected_user.main_user)[0] 745 | } 746 | 747 | Vue.set(state, 'selected_user', selected_user) 748 | Vue.set(state, 'transfers', transfers) 749 | } 750 | } 751 | 752 | let filter_last_transfer = (arr, main_user) => { 753 | let count = arr.filter(x => x.time_value > state.global_last_transfer).length 754 | return count 755 | } 756 | 757 | let getImagePath = (notifications) => { 758 | notifications = notifications > 9 ? `9+` : notifications 759 | return `${__static}/badges/badge_${notifications}.png` 760 | } 761 | 762 | let filter_duplictes = (transfers) => { 763 | let duplicates = transfers.filter(i => transfers.filter((ii) => ii.number === i.number).length > 1) 764 | if (duplicates.length > 0) { 765 | let list = [] 766 | for (let dup of duplicates) { 767 | if (list.indexOf(dup.number === -1)) { 768 | //transfers.indexOf(dup) 769 | } 770 | } 771 | } 772 | } 773 | 774 | function convert_float(text, asset) { 775 | return parseFloat(text.toString().replace(` ${asset}`, ``)) 776 | } 777 | 778 | export default { 779 | state, 780 | mutations, 781 | actions, 782 | getters 783 | } --------------------------------------------------------------------------------