├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
└── PULL_REQUEST_TEMPLATE
├── .gitignore
├── .jscsrc
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app
├── actions
│ ├── AliasesActions.js
│ ├── KeychainActions.js
│ ├── UIActions.js
│ └── index.js
├── app.global.css
├── app.html
├── app.icns
├── assets
│ ├── fonts
│ │ └── work-sans
│ │ │ ├── WorkSans-Bold.woff2
│ │ │ ├── WorkSans-Light.woff2
│ │ │ ├── WorkSans-Medium.woff2
│ │ │ ├── WorkSans-Regular.woff2
│ │ │ └── WorkSans.css
│ ├── images
│ │ ├── avatar.png
│ │ ├── avatar1.png
│ │ ├── avatar1.svg
│ │ ├── avatar10.png
│ │ ├── avatar10.svg
│ │ ├── avatar11.png
│ │ ├── avatar11.svg
│ │ ├── avatar12.png
│ │ ├── avatar12.svg
│ │ ├── avatar13.png
│ │ ├── avatar13.svg
│ │ ├── avatar14.png
│ │ ├── avatar14.svg
│ │ ├── avatar15.png
│ │ ├── avatar15.svg
│ │ ├── avatar16.png
│ │ ├── avatar16.svg
│ │ ├── avatar17.png
│ │ ├── avatar17.svg
│ │ ├── avatar18.png
│ │ ├── avatar18.svg
│ │ ├── avatar19.png
│ │ ├── avatar19.svg
│ │ ├── avatar2.png
│ │ ├── avatar2.svg
│ │ ├── avatar20.png
│ │ ├── avatar20.svg
│ │ ├── avatar21.png
│ │ ├── avatar21.svg
│ │ ├── avatar22.png
│ │ ├── avatar22.svg
│ │ ├── avatar23.png
│ │ ├── avatar23.svg
│ │ ├── avatar3.png
│ │ ├── avatar3.svg
│ │ ├── avatar4.png
│ │ ├── avatar4.svg
│ │ ├── avatar5.png
│ │ ├── avatar5.svg
│ │ ├── avatar6.png
│ │ ├── avatar6.svg
│ │ ├── avatar7.png
│ │ ├── avatar7.svg
│ │ ├── avatar8.png
│ │ ├── avatar8.svg
│ │ ├── avatar9.png
│ │ ├── avatar9.svg
│ │ ├── logo.png
│ │ ├── logo@2x.png
│ │ └── slant.svg
│ └── styles
│ │ ├── felony.css
│ │ ├── spinner.css
│ │ └── variables
│ │ ├── colors.js
│ │ └── utils.js
├── components
│ ├── Felony.js
│ ├── alias
│ │ ├── Alias.js
│ │ └── AliasComposerForm.js
│ ├── common
│ │ ├── Avatar.js
│ │ ├── Button.js
│ │ ├── Icon.js
│ │ ├── Overlay.js
│ │ ├── User.js
│ │ └── index.js
│ ├── composer
│ │ ├── Composer.js
│ │ ├── ComposerAliasForm.js
│ │ ├── ComposerAliasFormInput.js
│ │ ├── ComposerAliasSuccess.js
│ │ ├── ComposerForm.js
│ │ └── ComposerFormSubmit.js
│ ├── decrypt
│ │ ├── Decrypt.js
│ │ └── DecryptActions.js
│ ├── floating-button
│ │ ├── FloatingButton.js
│ │ ├── FloatingButtonItem.js
│ │ └── FloatingButtonItemLabel.js
│ ├── header
│ │ ├── Header.js
│ │ ├── HeaderKeyCopy.js
│ │ ├── HeaderKeyStatus.js
│ │ ├── HeaderKeyStatusSpinner.js
│ │ └── HeaderKeyStatusTooltip.js
│ ├── keychain
│ │ ├── Keychain.js
│ │ ├── KeychainComposerOpener.js
│ │ ├── KeychainList.js
│ │ └── KeychainListItem.js
│ └── output
│ │ └── Output.js
├── config
│ └── database.js
├── constants
│ ├── KeychainConstants.js
│ └── UIConstants.js
├── containers
│ ├── App.js
│ ├── ComposerContainer.js
│ ├── DevTools.js
│ ├── FloatingButtonContainer.js
│ ├── HeaderContainer.js
│ ├── KeychainContainer.js
│ └── OutputContainer.js
├── index.js
├── package.json
├── reducers
│ ├── index.js
│ ├── keychainReducer.js
│ └── uiReducer.js
├── routes.js
└── utils
│ ├── .gitkeep
│ ├── icons.js
│ └── pgp.js
├── main.development.js
├── package.js
├── package.json
├── server.js
├── webpack.config.base.js
├── webpack.config.development.js
├── webpack.config.electron.js
├── webpack.config.node.js
└── webpack.config.production.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015", "stage-0", "react" ]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{json,js,jsx,html,css}]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [.eslintrc]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | main.js
2 | build/**
3 | docs/build/**
4 | lib/**
5 | node_modules/**
6 | modules/**
7 | config.js
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | var IGNORE = 0
2 | var WARN = 1
3 | var ERROR = 2
4 |
5 | var ALWAYS = 'always'
6 | var NEVER = 'never'
7 | var OFF = 'off'
8 |
9 | module.exports = {
10 | extends: 'airbnb',
11 | parser: 'babel-eslint',
12 | env: {
13 | browser: true,
14 | node: true
15 | },
16 | rules: {
17 | 'react/jsx-curly-spacing': [ERROR, ALWAYS, { spacing: { objectLiterals: NEVER }}],
18 | 'react/jsx-filename-extension': [IGNORE],
19 | 'react/prop-types': [IGNORE],
20 | 'template-curly-spacing': [ERROR, ALWAYS],
21 | 'strict': [IGNORE],
22 | 'no-unused-expressions': IGNORE,
23 | 'no-unused-vars': [ERROR, { 'vars': 'local' }],
24 | 'arrow-body-style': [IGNORE, ALWAYS],
25 | 'camelcase': [ERROR],
26 | 'constructor-super': ERROR,
27 | 'quote-props': [ERROR, 'consistent'],
28 | 'no-underscore-dangle': [WARN],
29 | 'semi': [ERROR, NEVER],
30 | 'import/no-unresolved': [ERROR, { ignore: ['electron'] }],
31 | 'new-cap': [IGNORE],
32 | 'import/no-named-as-default': [IGNORE],
33 | 'import/no-extraneous-dependencies': [IGNORE],
34 | 'jsx-a11y/no-static-element-interactions': IGNORE
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 |
2 |
3 | #### What's this issue about?
4 |
5 |
6 |
11 |
12 |
13 |
19 |
20 | #### Expected behavior
21 |
22 |
23 | #### Actual behavior
24 |
25 |
26 | #### Steps to reproduce
27 |
28 |
29 | #### Environment
30 |
31 |
32 |
33 |
34 | #### About the Feature
35 |
36 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 |
2 |
3 | #### What's in this pull request?
4 |
5 |
6 | #### Bugs Squashed
7 |
8 | Resolves issue #x.
9 |
10 |
11 | #### Changes proposed
12 |
13 | -
14 | -
15 | -
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # OSX
30 | .DS_Store
31 |
32 | # App packaged
33 | dist
34 | release
35 | main.js
36 | main.js.map
37 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "airbnb",
3 | "excludeFiles": ["build/**", "node_modules/**", "config.js"],
4 | "disallowQuotedKeysInObjects": null,
5 | "validateQuoteMarks": null,
6 | "requireSemicolons": false,
7 | "disallowSemicolons": true,
8 | }
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | language: node_js
3 | notifications:
4 | email: false
5 | node_js:
6 | - "6.3.1"
7 |
8 | addons:
9 | apt:
10 | packages:
11 | - xvfb
12 | - g++-4.8
13 | - libgnome-keyring-dev
14 | sources:
15 | - ubuntu-toolchain-r-test
16 |
17 |
18 | env:
19 | - CXX=g++-4.8
20 |
21 | cache:
22 | directories:
23 | - node_modules
24 |
25 | before_install:
26 | - npm install -g npm@latest
27 |
28 | install:
29 | - export CXX="g++-4.8"
30 | - export DISPLAY=':99.0'
31 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
32 | - npm install
33 |
34 | script:
35 | - npm run dev
36 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Opening an Issue
2 |
3 | Please feel free to open an issue for any reason, including but not limited to bugs, feature requests, and help.
4 | If the issue is a duplicate, it will be flagged as such and subsequently closed by the Felony team.
5 | If not, expect some serious discussion!
6 | If you're reporting a bug, please include as much information as possible about your environment and the steps leading up to it. A good bug report is usually one that has enough information for the developers to replicate the bug.
7 |
8 | # Submitting a Pull Request
9 |
10 | Internally, we try to minimize the use of our core branch.
11 | If you're contributing and fixing an issue, please be sure that your push references the associated issue number.
12 |
13 | # Check Log File
14 |
15 | If your log file contains any information please attach it.
16 |
17 | - Linux: ~/.config/Felony/log.log
18 | - OS X: ~/Library/Logs/Felony/log.log
19 | - Windows: %AppData%/Felony/log.log
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Henry Boldizsar, Case Sandberg
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |
4 | **Felony is an open-source pgp keychain built on the modern web with Electron, React, and Redux.** Felony is the first PGP app that's easy for anyone to use, without a tutorial.
5 |
6 | []()
7 |
8 | [](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fhenryboldi%2Ffelony?ref=badge_large)
9 |
10 | ## Download Felony
11 | You can download compiled versions of Felony for Windows, macOS, and Linux from https://github.com/henryboldi/felony/releases. The app is currently in its pre-release stage, so it hasn't been fully tested on all platforms. Confirmed to be working on Mac, as that's what the developers own.
12 |
13 | ## How it works
14 | ### 1. Add public keys to your buddies list
15 | A public key is like a username - Adding someone’s public key to your buddies list lets you send them messages. You can find other public keys on markets like Keybase.io and Darknet.
16 | ### 2. Encrypt a message
17 | Select a recipient from your buddies list and compose a message. Only your chosen recipient(s) can read the message. Encrypted messages can be used to send sensitive information, such as an address, document, or anything intended to be read only by intended recipients.
18 | ### 3. Send the encrypted message anywhere
19 | You can send the encrypted message on any website! Send encrypted messages over Facebook Messenger, Twitter DMs, YouTube, Instagram, or anywhere else. **Felony is security when and where you want it.**
20 |
21 | ## Running Locally
22 | To run the development environment run
23 | ```
24 | npm run dev
25 | ```
26 | To package felony run
27 | ```
28 | npm run package
29 | ```
30 | To build for all platforms
31 | ```
32 | npm run package-all
33 | ```
34 | For more information check out [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate), which we used as a starting point.
35 |
36 | ## Feature Requests
37 | Have an idea for a feature you'd love to see in Felony? Create an issue and tag it as a feature request.
38 |
39 | ## Maintainers
40 |
41 | Maintained with ❤️ by [Sanil](https://github.com/TechyPeople), [Frank](https://github.com/frankcash).
42 |
43 | Created by [Henry](https://github.com/henryboldi) & [Case](https://github.com/casesandberg).
44 |
45 | > 100% inline styles via [ReactCSS](http://reactcss.com/)
46 |
--------------------------------------------------------------------------------
/app/actions/AliasesActions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/AliasesConstants'
2 | import db from '../config/database.js'
3 | import { generateKey } from '../../utils/pgp'
4 | let log = require('electron-log');
5 |
6 | export function addAlias(alias) {
7 | return async function (dispatch) {
8 | try {
9 | const generatedKey = await generateKey()
10 | const insertedAlias = await db('aliases').insert(alias)
11 | return dispatch({ type: types.ADD_KEY, alias: insertedAlias })
12 | } catch (err) {
13 | console.log(err)
14 | log.warn(err);
15 | }
16 | }
17 | }
18 |
19 | export function setAliases(aliases) {
20 | return { type: types.SET_KEYCHAIN, aliases }
21 | }
22 |
23 | export function fetchAliases() {
24 | return async function (dispatch) {
25 | try {
26 | const aliases = await db('aliases').value()
27 | return dispatch(setAliases(aliases))
28 | } catch (err) {
29 | log.warn(err);
30 |
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/actions/KeychainActions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/KeychainConstants'
2 | import db from '../config/database.js'
3 | let log = require('electron-log');
4 |
5 |
6 | export function addKey(key) {
7 | return async function (dispatch) {
8 | try {
9 | const insertedKey = await db('keychain').insert(key)
10 | return dispatch({ type: types.ADD_KEY, key: insertedKey })
11 | } catch (err) {
12 | console.log(err)
13 | log.warn(err);
14 | }
15 | }
16 | }
17 |
18 | export function setKeychain(keychain) {
19 | return { type: types.SET_KEYCHAIN, keychain }
20 | }
21 |
22 | export function fetchKeychain() {
23 | return async function (dispatch) {
24 | try {
25 | const keychain = await db('keychain').value()
26 | return dispatch(setKeychain(keychain))
27 | } catch (err) {
28 | log.warn(err);
29 |
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/actions/UIActions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/UIConstants'
2 |
3 | export function selectKey(id) {
4 | return { type: types.SELECT_KEY, id }
5 | }
6 |
7 | export function toggleComposer() {
8 | return { type: types.TOGGLE_COMPOSER }
9 | }
10 |
11 | export function toggleGeneratingKey() {
12 | return { type: types.TOGGLE_GENERATING_KEY }
13 | }
14 |
15 | export function toggleIsCopied() {
16 | return { type: types.TOGGLE_IS_COPIED }
17 | }
18 |
19 | export function clearSelectedKeys() {
20 | return { type: types.CLEAR_SELECTED_KEYS }
21 | }
22 |
23 | export function showComposerWithType(type) {
24 | return { type: types.SHOW_COMPOSER_WITH_TYPE, data: type }
25 | }
26 |
27 | export function setOutput(output) {
28 | return { type: types.SET_OUTPUT, output }
29 | }
30 |
--------------------------------------------------------------------------------
/app/actions/index.js:
--------------------------------------------------------------------------------
1 | export * from './KeychainActions'
2 | export * from './UIActions'
3 |
--------------------------------------------------------------------------------
/app/app.global.css:
--------------------------------------------------------------------------------
1 | body {
2 | position: relative;
3 | color: white;
4 | height: 100vh;
5 | background-color: #232C39;
6 | background-image: linear-gradient(45deg, rgba(0, 216, 255, .5) 10%, rgba(0, 1, 127, .7));
7 | font-family: Arial, Helvetica, Helvetica Neue;
8 | overflow-y: hidden;
9 | }
10 |
11 | h2 {
12 | margin: 0;
13 | font-size: 2.25rem;
14 | font-weight: bold;
15 | letter-spacing: -.025em;
16 | color: #fff;
17 | }
18 |
19 | p {
20 | font-size: 24px;
21 | }
22 |
23 | li {
24 | list-style: none;
25 | }
26 |
27 | a {
28 | color: white;
29 | opacity: .75;
30 | text-decoration: none;
31 | }
32 |
33 | a:hover {
34 | opacity: 1;
35 | text-decoration: none;
36 | cursor: pointer;
37 | }
38 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Felony
6 |
16 |
17 |
18 |
19 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/app.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/app.icns
--------------------------------------------------------------------------------
/app/assets/fonts/work-sans/WorkSans-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/fonts/work-sans/WorkSans-Bold.woff2
--------------------------------------------------------------------------------
/app/assets/fonts/work-sans/WorkSans-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/fonts/work-sans/WorkSans-Light.woff2
--------------------------------------------------------------------------------
/app/assets/fonts/work-sans/WorkSans-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/fonts/work-sans/WorkSans-Medium.woff2
--------------------------------------------------------------------------------
/app/assets/fonts/work-sans/WorkSans-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/fonts/work-sans/WorkSans-Regular.woff2
--------------------------------------------------------------------------------
/app/assets/fonts/work-sans/WorkSans.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'work-sans';
3 | font-weight: 300;
4 | src: url('./WorkSans-Light.woff2');
5 | }
6 |
7 | @font-face {
8 | font-family: 'work-sans';
9 | src: url('./WorkSans-Regular.woff2');
10 | }
11 |
12 | @font-face {
13 | font-family: 'work-sans';
14 | font-weight: 500;
15 | src: url('./WorkSans-Medium.woff2');
16 | }
17 |
18 | @font-face {
19 | font-family: 'work-sans';
20 | font-weight: 700;
21 | src: url('./WorkSans-Bold.woff2');
22 | }
23 |
--------------------------------------------------------------------------------
/app/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar.png
--------------------------------------------------------------------------------
/app/assets/images/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar1.png
--------------------------------------------------------------------------------
/app/assets/images/avatar1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar10.png
--------------------------------------------------------------------------------
/app/assets/images/avatar10.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar11.png
--------------------------------------------------------------------------------
/app/assets/images/avatar11.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar12.png
--------------------------------------------------------------------------------
/app/assets/images/avatar12.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar13.png
--------------------------------------------------------------------------------
/app/assets/images/avatar13.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar14.png
--------------------------------------------------------------------------------
/app/assets/images/avatar14.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar15.png
--------------------------------------------------------------------------------
/app/assets/images/avatar15.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar16.png
--------------------------------------------------------------------------------
/app/assets/images/avatar16.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar17.png
--------------------------------------------------------------------------------
/app/assets/images/avatar17.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar18.png
--------------------------------------------------------------------------------
/app/assets/images/avatar18.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar19.png
--------------------------------------------------------------------------------
/app/assets/images/avatar19.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar2.png
--------------------------------------------------------------------------------
/app/assets/images/avatar2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar20.png
--------------------------------------------------------------------------------
/app/assets/images/avatar20.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar21.png
--------------------------------------------------------------------------------
/app/assets/images/avatar21.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar22.png
--------------------------------------------------------------------------------
/app/assets/images/avatar22.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar23.png
--------------------------------------------------------------------------------
/app/assets/images/avatar23.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar3.png
--------------------------------------------------------------------------------
/app/assets/images/avatar3.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar4.png
--------------------------------------------------------------------------------
/app/assets/images/avatar4.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar5.png
--------------------------------------------------------------------------------
/app/assets/images/avatar5.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar6.png
--------------------------------------------------------------------------------
/app/assets/images/avatar6.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar7.png
--------------------------------------------------------------------------------
/app/assets/images/avatar7.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar8.png
--------------------------------------------------------------------------------
/app/assets/images/avatar8.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/avatar9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/avatar9.png
--------------------------------------------------------------------------------
/app/assets/images/avatar9.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/logo.png
--------------------------------------------------------------------------------
/app/assets/images/logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/assets/images/logo@2x.png
--------------------------------------------------------------------------------
/app/assets/images/slant.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/styles/felony.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | -webkit-overflow-scrolling: touch;
3 | overflow: hidden;
4 | font-family: 'work-sans';
5 | }
6 |
--------------------------------------------------------------------------------
/app/assets/styles/spinner.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | width: 20px;
3 | height: 20px;
4 |
5 | position: relative;
6 | align-self: center;
7 | margin-top: 2px;
8 | }
9 |
10 | .double-bounce1, .double-bounce2 {
11 | width: 100%;
12 | height: 100%;
13 | border-radius: 50%;
14 | background-color: #fff;
15 | opacity: 0.6;
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 |
20 | -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
21 | animation: sk-bounce 2.0s infinite ease-in-out;
22 | }
23 |
24 | .double-bounce2 {
25 | -webkit-animation-delay: -1.0s;
26 | animation-delay: -1.0s;
27 | }
28 |
29 | @-webkit-keyframes sk-bounce {
30 | 0%, 100% { -webkit-transform: scale(0.0) }
31 | 50% { -webkit-transform: scale(1.0) }
32 | }
33 |
34 | @keyframes sk-bounce {
35 | 0%, 100% {
36 | transform: scale(0.0);
37 | -webkit-transform: scale(0.0);
38 | } 50% {
39 | transform: scale(1.0);
40 | -webkit-transform: scale(1.0);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/assets/styles/variables/colors.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | bgDark: '#2B272C',
3 | bgLight: '#37373C',
4 | bgGrey: '#EAE4E4',
5 | offBgLight: '#DEE3ED',
6 | primary: '#E4212A',
7 | primaryGradient: 'linear-gradient(to left bottom, #E4212A 0%, #B61E4F 100%)',
8 | green: '#00E676',
9 | }
10 |
11 | export default colors
12 |
--------------------------------------------------------------------------------
/app/assets/styles/variables/utils.js:
--------------------------------------------------------------------------------
1 | export const spacing = {
2 | s: 8,
3 | m: 12,
4 | l: 16,
5 | statusBarHeight: 20,
6 | }
7 |
8 | export const sizing = {
9 | avatar: 34,
10 | }
11 |
--------------------------------------------------------------------------------
/app/components/Felony.js:
--------------------------------------------------------------------------------
1 | import 'normalize.css'
2 | import React, { Component } from 'react'
3 | import ReactCSS from 'reactcss'
4 |
5 | import '../assets/fonts/work-sans/WorkSans.css'
6 | import '../assets/styles/felony.css'
7 | import '../assets/styles/spinner.css'
8 | import colors from '../assets/styles/variables/colors'
9 |
10 | import HeaderContainer from '../containers/HeaderContainer'
11 | import FloatingButtonContainer from '../containers/FloatingButtonContainer'
12 | import ComposerContainer from '../containers/ComposerContainer'
13 | import OutputContainer from '../containers/OutputContainer'
14 | import KeychainContainer from '../containers/KeychainContainer'
15 |
16 | export class Felony extends Component {
17 | state = {
18 | selected: [], // replaced with redux
19 | }
20 |
21 | classes() { // eslint-disable-line
22 | return {
23 | 'default': {
24 | app: {
25 |
26 | // NOTE: Set here and not in felony.css in order to use color variable
27 | background: colors.bgLight,
28 | position: 'absolute',
29 | width: '100%',
30 | height: '100%',
31 | color: 'white',
32 | },
33 | header: {
34 | position: 'fixed',
35 | top: '0px',
36 | left: '0px',
37 | right: '0px',
38 | },
39 | },
40 | }
41 | }
42 |
43 | // this will be replaced with Redux
44 | handleAddToSelected = (selected) => {
45 | this.setState({
46 | selected: this.state.selected.concat([selected]),
47 | })
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | )
64 | }
65 | }
66 |
67 | export default ReactCSS(Felony)
68 |
--------------------------------------------------------------------------------
/app/components/alias/Alias.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import { User } from '../common/index'
5 |
6 | class Alias extends Component {
7 | classes() { // eslint-disable-line
8 | return {
9 | 'default': {
10 | user: {
11 | color: '#fff',
12 | flex: '1',
13 | fontWeight: '500',
14 | },
15 | },
16 | }
17 | }
18 |
19 | render() {
20 | return (
21 |
22 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default ReactCSS(Alias)
33 |
--------------------------------------------------------------------------------
/app/components/alias/AliasComposerForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import colors from '../../assets/styles/variables/colors'
5 |
6 | class AliasComposer extends Component {
7 | classes() { // eslint-disable-line
8 | return {
9 | 'default': {
10 | text: {
11 | flex: '1',
12 | position: 'relative',
13 | },
14 | textarea: {
15 | Absolute: '23px 10px 10px 10px',
16 | border: 'none',
17 | outline: 'none',
18 | width: '94%',
19 | boxSizing: 'border-box',
20 | padding: '10px 3%',
21 | color: '#333',
22 | borderRadius: '2px',
23 | fontSize: '14px',
24 | fontFamily: 'Andale Mono',
25 | },
26 | actions: {
27 | marginTop: '-10px',
28 | height: '40px',
29 | display: 'flex',
30 | alignItems: 'center',
31 | justifyContent: 'space-between',
32 | padding: '0 10px',
33 | },
34 | link: {
35 | textDecoration: 'none',
36 | cursor: 'pointer',
37 | },
38 | cancel: {
39 | color: '#999',
40 | },
41 | confirm: {
42 | color: colors.primary,
43 | },
44 | },
45 | }
46 | }
47 |
48 | handleCancel = () => {
49 | this.props.toggleAliasComposer()
50 | }
51 |
52 | handleKeyDown = (e) => {
53 | (e.keyCode === 27 && this.props.isShowingAliasComposer) && this.handleCancel()
54 | }
55 |
56 | render() {
57 | return (
58 |
67 | )
68 | }
69 | }
70 |
71 | export default ReactCSS(AliasComposer)
72 |
--------------------------------------------------------------------------------
/app/components/common/Avatar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | class Avatar extends Component {
5 | classes() {
6 | return {
7 | 'default': {
8 | avatar: {
9 | width: '100%',
10 | paddingBottom: '100%',
11 | borderRadius: '50%',
12 | background: `url(${ this.props.href }) #666`,
13 | backgroundSize: 'cover',
14 | },
15 | },
16 | }
17 | }
18 |
19 | render() {
20 | return
21 | }
22 | }
23 |
24 | export default ReactCSS(Avatar)
25 |
--------------------------------------------------------------------------------
/app/components/common/Button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import { spacing } from '../../assets/styles/variables/utils'
5 |
6 | class Button extends Component {
7 | classes() {
8 | return {
9 | 'default': {
10 | button: {
11 | height: '30px',
12 | padding: `0 ${ spacing.s }px`,
13 | borderRadius: '4px',
14 | backgroundColor: this.props.background,
15 | color: this.props.color || 'inherit',
16 | display: 'flex',
17 | alignItems: 'center',
18 | },
19 | },
20 | }
21 | }
22 |
23 | render() {
24 | return { this.props.children || this.props.label }
25 | }
26 | }
27 |
28 | export default ReactCSS(Button)
29 |
--------------------------------------------------------------------------------
/app/components/common/Icon.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-danger */
2 | import React, { Component } from 'react'
3 | import ReactCSS from 'reactcss'
4 | import { getIcon } from '../../utils/icons'
5 |
6 | class Icon extends Component {
7 | classes() {
8 | return {
9 | 'default': {
10 | icon: {
11 | color: this.props.color,
12 | display: 'inline-block',
13 | },
14 | },
15 | }
16 | }
17 |
18 | render() {
19 | return
20 | }
21 | }
22 |
23 | export default ReactCSS(Icon)
24 |
--------------------------------------------------------------------------------
/app/components/common/Overlay.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-static-element-interactions */
2 | import React, { Component } from 'react'
3 | import ReactCSS from 'reactcss'
4 |
5 | class Overlay extends Component {
6 | classes() { // eslint-disable-line
7 | return {
8 | 'default': {
9 | overlay: {
10 | position: 'fixed',
11 | top: '0px',
12 | left: '0px',
13 | right: '0px',
14 | bottom: '0px',
15 | background: 'rgba(0, 0, 0, 0.7)', // TODO: color package for hex code opacity
16 | },
17 | },
18 | }
19 | }
20 |
21 | handleClick = () => this.props.onClick && this.props.onClick()
22 |
23 | render() {
24 | return
25 | }
26 | }
27 |
28 | export default ReactCSS(Overlay)
29 |
--------------------------------------------------------------------------------
/app/components/common/User.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import { spacing, sizing } from '../../assets/styles/variables/utils'
5 | import colors from '../../assets/styles/variables/colors'
6 |
7 | import Avatar from './Avatar'
8 |
9 | class User extends Component {
10 | classes() { // eslint-disable-line
11 | return {
12 | 'default': {
13 | user: {
14 | display: 'flex',
15 | alignItems: 'center',
16 | },
17 | avatarWrap: {
18 | width: sizing.avatar,
19 | position: 'relative',
20 | },
21 | outline: {
22 | Absolute: '0px 0px 0px 0px',
23 | borderRadius: '50%',
24 | opacity: '0',
25 | border: `2px solid ${ colors.primary }`,
26 | },
27 | avatar: {
28 | transform: 'scale(1)',
29 | transition: 'transform 100ms ease-in-out',
30 | borderRadius: '50%',
31 | backgroundColor: '#666666',
32 | },
33 | name: {
34 | flex: '1',
35 | marginLeft: spacing.s,
36 | fontSize: '16px',
37 | whiteSpace: 'nowrap',
38 | overflow: 'hidden',
39 | textOverflow: 'ellipsis',
40 | },
41 | },
42 | 'active': {
43 | avatar: {
44 | transform: 'scale(.6)',
45 | },
46 | outline: {
47 | opacity: '1',
48 | },
49 | },
50 | }
51 | }
52 |
53 | render() {
54 | return (
55 |
56 |
62 |
63 | { this.props.name }
64 |
65 |
66 | )
67 | }
68 | }
69 |
70 | export default ReactCSS(User)
71 |
--------------------------------------------------------------------------------
/app/components/common/index.js:
--------------------------------------------------------------------------------
1 | export { default as Avatar } from './Avatar'
2 | export { default as Button } from './Button'
3 | export { default as Icon } from './Icon'
4 | export { default as Overlay } from './Overlay'
5 | export { default as User } from './User'
6 |
--------------------------------------------------------------------------------
/app/components/composer/Composer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import ComposerForm from './ComposerForm'
5 | import ComposerAliasForm from './ComposerAliasForm'
6 |
7 | import colors from '../../assets/styles/variables/colors'
8 |
9 | class Composer extends Component {
10 | classes() { // eslint-disable-line
11 | return {
12 | 'default': {
13 | wrap: {
14 | position: 'fixed',
15 | top: '0px',
16 | right: '0px',
17 | bottom: '0px',
18 | left: '0px',
19 | zIndex: '13',
20 | flexDirection: 'column',
21 | background: colors.bgDark,
22 | display: 'flex',
23 | transform: 'translateY(100%)',
24 | opacity: '0px',
25 | transition: 'all 200ms ease-in-out',
26 | },
27 | },
28 | 'isShowingComposer': {
29 | wrap: {
30 | transform: 'translateY(0)',
31 | opacity: '1',
32 | },
33 | },
34 | }
35 | }
36 |
37 | handleCancel = () => {
38 | this.props.toggleComposer()
39 | }
40 |
41 | render() {
42 | return (
43 |
44 | { this.props.composerType === 'alias' ?
45 |
49 | :
50 |
54 | }
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | export default ReactCSS(Composer)
62 |
--------------------------------------------------------------------------------
/app/components/composer/ComposerAliasForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import keytar from 'keytar'
4 | import { generateKey } from '../../utils/pgp'
5 |
6 | import ComposerAliasFormInput from './ComposerAliasFormInput'
7 | import ComposerFormSubmit from './ComposerFormSubmit'
8 | import ComposerAliasSuccess from './ComposerAliasSuccess'
9 |
10 | import colors from '../../assets/styles/variables/colors'
11 |
12 | class ComposerAliasForm extends Component {
13 | state = {
14 | submitted: false,
15 | invalidName: false,
16 | invalidEmail: false,
17 | invalidPassphrase: false,
18 | }
19 |
20 | classes() { // eslint-disable-line
21 | return {
22 | 'default': {
23 | wrap: {
24 | position: 'fixed',
25 | top: '0px',
26 | right: '0px',
27 | bottom: '0px',
28 | left: '0px',
29 | zIndex: '13',
30 | flexDirection: 'column',
31 | display: 'flex',
32 | },
33 | welcome: {
34 | width: '35px',
35 | margin: '0 auto',
36 | marginTop: '43px',
37 | marginBottom: '-70px',
38 | },
39 | instructions: {
40 | width: '200px',
41 | textAlign: 'center',
42 | margin: '0 auto',
43 | marginTop: '0px',
44 | marginBottom: '20px',
45 | },
46 | form: {
47 | flex: '1',
48 | position: 'relative',
49 | margin: '0 20px',
50 | },
51 | formItem: {
52 | display: 'flex',
53 | flexDirection: 'column',
54 | marginBottom: '15px',
55 | },
56 | input: {
57 | border: 'solid 2px #CBC5C5',
58 | backgroundColor: 'transparent',
59 | outline: 'none',
60 | width: '100%',
61 | boxSizing: 'border-box',
62 | padding: '10px 3%',
63 | color: '#333',
64 | borderRadius: '5px',
65 | fontSize: '14px',
66 | fontFamily: 'Andale Mono',
67 | flexGrow: '1',
68 | },
69 | actions: {
70 | marginTop: '-10px',
71 | height: '40px',
72 | padding: '20px',
73 | },
74 | confirm: {
75 | color: '#fff',
76 | padding: '10px 30px',
77 | borderRadius: '5px',
78 | backgroundColor: colors.primary,
79 | textDecoration: 'none',
80 | cursor: 'pointer',
81 | textAlign: 'center',
82 | display: 'block',
83 | transition: '200ms ease-in-out',
84 | },
85 | bgGrey: {
86 | backgroundColor: colors.bgGrey,
87 | color: colors.bgDark,
88 | position: 'fixed',
89 | left: '0',
90 | right: '0',
91 | bottom: '0',
92 | },
93 | },
94 | 'hover': {
95 | input: {
96 | border: 'solid 1px red',
97 | },
98 | confirm: {
99 | backgroundColor: '#BF1B23',
100 | },
101 | },
102 | }
103 | }
104 |
105 | handleKeyDown = (e) => {
106 | if (e.keyCode === 13) {
107 | this.handleConfirm()
108 | }
109 | }
110 |
111 | handleConfirm = async () => {
112 | const name = this.form[0].value
113 | const email = this.form[1].value
114 | const passphrase = this.form[2].value
115 |
116 | if (name === '' || email === '' || passphrase === '') {
117 | this.setState({ invalidName: false, invalidEmail: false, invalidPassphrase: false })
118 |
119 | if (name === '') {
120 | this.setState({ invalidName: true })
121 | }
122 | if (email === '') {
123 | this.setState({ invalidEmail: true })
124 | }
125 | if (passphrase === '') {
126 | this.setState({ invalidPassphrase: true })
127 | }
128 | return
129 | }
130 |
131 | const notification = {
132 | title: 'Keys Done Generating',
133 | body: 'Copy your public key by clicking the icon to the right of your name.',
134 | }
135 |
136 | this.props.toggleGeneratingKey()
137 | this.setState({ submitted: true })
138 | await this.props.addKey({ id: 999, name, privateKeyArmored: 'generating' })
139 | const key = await generateKey({ name, email }, passphrase)
140 | key.avatar = 9
141 | key.id = 999
142 |
143 | new Notification(notification.title, notification) // eslint-disable-line no-new
144 | keytar.addPassword('felony', `${ name } <${ email }>`, passphrase)
145 | await this.props.addKey(key)
146 | this.props.toggleGeneratingKey()
147 | }
148 |
149 | render() {
150 | return (
151 |
152 |

157 |
161 |
162 | { !this.state.submitted ?
163 |
164 |
To get started, generate your keys.
165 |
192 |
193 |
197 |
198 |
199 | :
200 |
203 | }
204 |
205 |
206 | )
207 | }
208 | }
209 |
210 | export default ReactCSS.Hover(ReactCSS(ComposerAliasForm))
211 |
--------------------------------------------------------------------------------
/app/components/composer/ComposerAliasFormInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | class ComposerAliasFormInput extends Component {
5 | state = { focus: false }
6 |
7 | classes() { // eslint-disable-line
8 | return {
9 | 'default': {
10 | input: {
11 | border: 'solid 2px #CBC5C5',
12 | backgroundColor: 'transparent',
13 | outline: 'none',
14 | width: '100%',
15 | boxSizing: 'border-box',
16 | padding: '10px 3%',
17 | color: '#333',
18 | borderRadius: '5px',
19 | fontSize: '14px',
20 | fontFamily: 'Andale Mono',
21 | flexGrow: '1',
22 | transition: '200ms ease-in-out',
23 | },
24 | error: {
25 | border: 'solid 2px #FF1122',
26 | },
27 | },
28 | 'hover': {
29 | input: {
30 | border: 'solid 2px #A7A4A4',
31 | },
32 | },
33 | 'focus': {
34 | input: {
35 | border: 'solid 2px #A7A4A4',
36 | },
37 | },
38 | }
39 | }
40 |
41 | activations() {
42 | return {
43 | 'focus': this.state.focus,
44 | }
45 | }
46 |
47 | render() {
48 | return (
49 | { this.setState({ focus: true }) } }
55 | onBlur={ () => { this.setState({ focus: false }) } }
56 | />
57 | )
58 | }
59 | }
60 |
61 | export default ReactCSS.Hover(ReactCSS(ComposerAliasFormInput))
62 |
--------------------------------------------------------------------------------
/app/components/composer/ComposerAliasSuccess.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import ComposerFormSubmit from './ComposerFormSubmit'
5 |
6 | import colors from '../../assets/styles/variables/colors'
7 |
8 | class ComposerAliasSuccess extends Component {
9 | classes() { // eslint-disable-line
10 | return {
11 | 'default': {
12 | title: {
13 | textAlign: 'center',
14 | fontSize: '24px',
15 | width: '80%',
16 | margin: '0 auto',
17 | marginBottom: '20px',
18 | },
19 | body: {
20 | width: '80%',
21 | textAlign: 'center',
22 | margin: '0px auto 20px',
23 | },
24 | iframe: {
25 | border: 'none',
26 | margin: '0 auto',
27 | display: 'block',
28 | marginTop: '35px',
29 | marginBottom: '35px',
30 | },
31 | actions: {
32 | marginTop: '-10px',
33 | height: '40px',
34 | padding: '20px',
35 | },
36 | confirm: {
37 | color: '#fff',
38 | padding: '10px 30px',
39 | borderRadius: '5px',
40 | backgroundColor: colors.primary,
41 | textDecoration: 'none',
42 | cursor: 'pointer',
43 | textAlign: 'center',
44 | display: 'block',
45 | transition: '200ms ease-in-out',
46 | },
47 | },
48 | 'hover': {
49 | input: {
50 | border: 'solid 1px red',
51 | },
52 | confirm: {
53 | backgroundColor: '#BF1B23',
54 | },
55 | },
56 | }
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
Your keys are generating.
63 |
In the meantime, support easy to use encryption 😊
64 |
72 |
73 |
77 |
78 |
79 | )
80 | }
81 | }
82 |
83 | export default ReactCSS.Hover(ReactCSS(ComposerAliasSuccess))
84 |
--------------------------------------------------------------------------------
/app/components/composer/ComposerForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import { readArmored, encrypt, decrypt, sign, verify } from '../../utils/pgp'
4 |
5 | import { Icon } from '../common/index'
6 | import ComposerFormSubmit from './ComposerFormSubmit'
7 |
8 | import colors from '../../assets/styles/variables/colors'
9 |
10 | class ComposerForm extends Component {
11 |
12 | classes() { // eslint-disable-line
13 | return {
14 | 'default': {
15 | wrap: {
16 | position: 'fixed',
17 | top: '0px',
18 | right: '0px',
19 | bottom: '0px',
20 | left: '0px',
21 | zIndex: '13',
22 | flexDirection: 'column',
23 | display: 'flex',
24 | background: '#37373C',
25 | },
26 | head: {
27 | background: '#2B272C',
28 | fontWeight: '300',
29 | padding: '10px 20px',
30 | paddingTop: '35px',
31 | },
32 |
33 | title: {
34 | fontSize: '20px',
35 | fontWeight: '400',
36 | },
37 | icon: {
38 | width: '20px',
39 | marginRight: '10px',
40 | display: 'inline-block',
41 | verticalAlign: 'middle',
42 | },
43 | subtitle: {
44 | fontSize: '16px',
45 | color: 'rgba(255,255,255,.7)',
46 | marginTop: '6px',
47 | marginBottom: '3px',
48 | lineHeight: '23px',
49 | },
50 | text: {
51 | flex: '1',
52 | position: 'relative',
53 | margin: '0 20px',
54 | marginTop: '-10px',
55 | },
56 | textarea: {
57 | Absolute: '23px auto 10px auto',
58 | border: 'none',
59 | outline: 'none',
60 | width: '100%',
61 | boxSizing: 'border-box',
62 | padding: '10px 3%',
63 | color: '#333',
64 | borderRadius: '2px',
65 | fontSize: '14px',
66 | fontFamily: 'Andale Mono',
67 | marginTop: '5px',
68 | },
69 | actions: {
70 | marginTop: '-18px',
71 | height: '55px',
72 | padding: '20px',
73 | },
74 | confirm: {
75 | color: '#fff',
76 | padding: '10px 20px',
77 | borderRadius: '5px',
78 | backgroundColor: colors.primary,
79 | textDecoration: 'none',
80 | cursor: 'pointer',
81 | textAlign: 'center',
82 | display: 'block',
83 | },
84 | cancel: {
85 | color: 'rgba(255,255,255,.4)',
86 | textAlign: 'center',
87 | display: 'block',
88 | marginTop: '7px',
89 | cursor: 'pointer',
90 | },
91 | },
92 | }
93 | }
94 |
95 | pgp = (pgpFn, ...options) => {
96 | return () => {
97 | const data = this.textarea.value
98 | const output = pgpFn(data, ...options)
99 | return output
100 | }
101 | }
102 |
103 | readKey = this.pgp(readArmored)
104 |
105 | getRandomInt = (min, max) => {
106 | return Math.floor(Math.random() * ((max - min) + 1)) + min
107 | }
108 |
109 | handleAddKey = async () => {
110 | const avatar = this.getRandomInt(1, 23)
111 | const key = await this.readKey()
112 | key.avatar = avatar
113 | await this.props.addKey(key)
114 | }
115 |
116 | handleEncrypt = async () => {
117 | const encryptMessage = await this.pgp(
118 | encrypt,
119 | this.props.selectedKeychain,
120 | this.props.alias.privateKeyArmored,
121 | )()
122 | this.props.setOutput(encryptMessage)
123 | }
124 |
125 | handleDecrypt = async () => {
126 | const message = await this.pgp(decrypt, this.props.alias.privateKeyArmored)()
127 | this.props.setOutput(message)
128 | }
129 |
130 | handleSign = async () => {
131 | const signedMessage = await this.pgp(sign, this.props.alias.privateKeyArmored)()
132 | this.props.setOutput(signedMessage)
133 | }
134 |
135 | handleVerify = async () => {
136 | const match = await this.pgp(verify, this.props.keychain)()
137 | if (typeof match.name === 'undefined') {
138 | this.props.setOutput('The author of this message appears to not be in your contact list')
139 | } else {
140 | this.props.setOutput(`Signed message is verified to match: ${ match.name } <${ match.email }>`)
141 | }
142 | }
143 |
144 | getProps = () => {
145 | return {
146 | 'encrypt': {
147 | title: 'Encrypt',
148 | subtitle: 'Encrypting creates a locked message for your recipients.',
149 | acceptLabel: 'Encrypt',
150 | onAccept: this.handleEncrypt,
151 | placeholder: 'Write a message',
152 | icon: 'encrypt',
153 | },
154 | 'decrypt': {
155 | title: 'Decrypt',
156 | subtitle: 'Decrypting a message lets you read for you.',
157 | acceptLabel: 'Decrypt',
158 | onAccept: this.handleDecrypt,
159 | placeholder: 'Paste an encrypted message',
160 | icon: 'decrypt',
161 | },
162 | 'sign': {
163 | title: 'Sign',
164 | subtitle: 'Signing a message lets others know that it’s really you.',
165 | acceptLabel: 'Sign',
166 | onAccept: this.handleSign,
167 | placeholder: 'Write a message',
168 | icon: 'sign',
169 | },
170 | 'verify': {
171 | title: 'Verify',
172 | subtitle: 'Verifying confirms who authored a message is as they claim.',
173 | acceptLabel: 'Verify',
174 | onAccept: this.handleVerify,
175 | placeholder: 'Paste a signed message',
176 | icon: 'verify',
177 | },
178 | 'add key': {
179 | title: 'Add Key',
180 | subtitle: 'Add someone’s public key to your buddies list.',
181 | acceptLabel: 'Add',
182 | onAccept: this.handleAddKey,
183 | placeholder: 'Paste a public key',
184 | icon: 'add-key',
185 | },
186 | }[this.props.composerType] || {}
187 | }
188 |
189 | componentWillReceiveProps = (nextProps) => {
190 | if (nextProps.isShowingComposer) {
191 | this.textarea.value = ''
192 | this.textarea.focus()
193 | }
194 | }
195 |
196 | render() {
197 | const props = this.getProps()
198 |
199 | return (
200 |
201 |
202 |
203 |
204 |
205 |
206 | { props.title }
207 |
208 |
{ props.subtitle }
209 |
210 |
211 |
218 |
229 |
230 | )
231 | }
232 | }
233 |
234 | export default ReactCSS(ComposerForm)
235 |
--------------------------------------------------------------------------------
/app/components/composer/ComposerFormSubmit.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import colors from '../../assets/styles/variables/colors'
5 |
6 | class ComposerFormSubmit extends Component {
7 | state = { value: this.props.value }
8 |
9 | classes() { // eslint-disable-line
10 | return {
11 | 'default': {
12 | confirm: {
13 | color: '#fff',
14 | padding: '10px 30px',
15 | borderRadius: '5px',
16 | backgroundColor: colors.primary,
17 | textDecoration: 'none',
18 | cursor: 'pointer',
19 | textAlign: 'center',
20 | display: 'block',
21 | transition: '200ms ease-in-out',
22 | },
23 | },
24 | 'hover': {
25 | confirm: {
26 | backgroundColor: '#BF1B23',
27 | },
28 | },
29 | }
30 | }
31 |
32 | componentWillReceiveProps = (nextProps) => {
33 | this.setState({ value: nextProps.value })
34 | }
35 |
36 | handleClick = (clickFn) => {
37 | return () => {
38 | this.setState({ value: 'Hold Your Breath...' })
39 | clickFn()
40 | }
41 | }
42 |
43 | render() {
44 | return (
45 |
49 | { this.state.value }
50 |
51 | )
52 | }
53 | }
54 |
55 | export default ReactCSS.Hover(ReactCSS(ComposerFormSubmit))
56 |
--------------------------------------------------------------------------------
/app/components/decrypt/Decrypt.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class Decrypt extends Component { // eslint-disable-line
4 | render() {
5 | return
6 | }
7 | }
8 |
9 | export default Decrypt
10 |
--------------------------------------------------------------------------------
/app/components/decrypt/DecryptActions.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class DecryptActions extends Component { // eslint-disable-line
4 | render() {
5 | return
6 | }
7 | }
8 |
9 | export default DecryptActions
10 |
--------------------------------------------------------------------------------
/app/components/floating-button/FloatingButton.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import { spacing } from '../../assets/styles/variables/utils'
5 |
6 | import { Icon } from '../common/index'
7 | import FloatingButtonItem from './FloatingButtonItem'
8 |
9 | class FloatingButton extends Component {
10 | state = { isShowingActions: false }
11 |
12 | classes() { // eslint-disable-line
13 | return {
14 | 'default': {
15 | wrap: {
16 | position: 'fixed',
17 | right: `${ spacing.m }px`,
18 | bottom: `${ spacing.m }px`,
19 | },
20 | buttons: {
21 | zIndex: '9990',
22 | display: 'flex',
23 | flexDirection: 'column',
24 | alignItems: 'center',
25 | marginTop: '10px',
26 | },
27 | item: {
28 | marginTop: '10px',
29 | },
30 | },
31 | }
32 | }
33 |
34 | handleOpen = () => this.setState({ isShowingActions: true })
35 |
36 | handleClose = () => this.setState({ isShowingActions: false })
37 |
38 | renderFloatingButtonDiv(type) {
39 | const action = type.toLowerCase()
40 | const iconName = action.replace(/ /g, '-')
41 |
42 | return (
43 | this.props.showComposer(action) }>
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | render() {
52 | return (
56 | { this.state.isShowingActions ?
57 |
58 |
59 | { this.renderFloatingButtonDiv('Add Key') }
60 | { this.renderFloatingButtonDiv('Verify') }
61 | { this.renderFloatingButtonDiv('Sign') }
62 | { this.renderFloatingButtonDiv('Decrypt') }
63 |
64 |
65 | :
66 |
67 |
68 |
69 |
70 |
71 | }
72 |
)
73 | }
74 | }
75 |
76 | export default ReactCSS(FloatingButton)
77 |
--------------------------------------------------------------------------------
/app/components/floating-button/FloatingButtonItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import dynamics from 'dynamics.js'
4 |
5 | import colors from '../../assets/styles/variables/colors'
6 | import FloatingButtonItemLabel from './FloatingButtonItemLabel'
7 |
8 | class FloatingButtonItem extends Component {
9 | classes() { // eslint-disable-line
10 | const floatingButtonSize = 40
11 |
12 | return {
13 | 'default': {
14 | wrap: {
15 | display: 'inline-flex',
16 | alignItems: 'center',
17 | position: 'relative',
18 | transform: 'scale(0.5)', // for animation
19 | },
20 | label: {
21 | position: 'absolute',
22 | right: '60px',
23 | top: '0px',
24 | bottom: '0px',
25 | },
26 | button: {
27 | backgroundColor: colors.primary,
28 | height: `${ floatingButtonSize }px`,
29 | width: `${ floatingButtonSize }px`, // TODO: use utils for sizing
30 | borderRadius: '100%',
31 | display: 'flex',
32 | alignItems: 'center',
33 | justifyContent: 'center',
34 | boxShadow: '0px 4px 10px 0px rgba(221, 33, 48, 0.33)',
35 | transition: 'box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
36 | cursor: 'pointer',
37 | },
38 | buttonContent: {
39 | width: '24px',
40 | },
41 | },
42 | 'hover': {
43 | button: {
44 | boxShadow: '0 8px 17px 0 rgba(221, 33, 48, 0.7)',
45 | },
46 | },
47 | 'size-large': {
48 | button: {
49 | height: `${ floatingButtonSize * 1.3 }px`,
50 | width: `${ floatingButtonSize * 1.3 }px`,
51 | right: '900px',
52 | },
53 | label: {
54 | right: '67px',
55 | },
56 | },
57 | }
58 | }
59 |
60 | componentDidMount = () => {
61 | dynamics.animate(this.wrap, {
62 | scale: 1,
63 | }, {
64 | type: dynamics.spring,
65 | duration: 500,
66 | friction: 400,
67 | })
68 | }
69 |
70 | render() {
71 | return (
72 |
87 | )
88 | }
89 | }
90 |
91 | export default ReactCSS.Hover(ReactCSS(FloatingButtonItem))
92 |
--------------------------------------------------------------------------------
/app/components/floating-button/FloatingButtonItemLabel.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import colors from '../../assets/styles/variables/colors'
5 |
6 | class FloatingButtonItemLabel extends Component {
7 | classes() { // eslint-disable-line
8 | return {
9 | 'default': {
10 | wrap: {
11 | display: 'flex',
12 | alignItems: 'center',
13 | position: 'absolute',
14 | top: '0px',
15 | bottom: '0px',
16 | right: '0px',
17 | },
18 | label: {
19 | backgroundColor: '#87878A',
20 | borderRadius: '3px',
21 | padding: '4px',
22 | color: colors.bgDark,
23 | whiteSpace: 'nowrap',
24 | fontSize: '14px',
25 | boxShadow: `0px 1px 0px 0px ${ colors.bgDark }`,
26 | transition: 'background-color 0.2s ease-in-out',
27 | },
28 | },
29 | 'hover': {
30 | label: {
31 | backgroundColor: '#C3C3C5',
32 | },
33 | },
34 | }
35 | }
36 |
37 | render() {
38 | return (
39 |
40 | { this.props.label }
41 |
42 | )
43 | }
44 | }
45 |
46 | export default ReactCSS(FloatingButtonItemLabel)
47 |
--------------------------------------------------------------------------------
/app/components/header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import colors from '../../assets/styles/variables/colors'
5 | import { spacing } from '../../assets/styles/variables/utils'
6 |
7 | import Alias from '../alias/Alias'
8 | import HeaderKeyStatus from './HeaderKeyStatus'
9 |
10 | class Header extends Component {
11 | classes() { // eslint-disable-line
12 | return {
13 | 'default': {
14 | header: {
15 | background: colors.bgDark,
16 | display: 'flex',
17 | justifyContent: 'space-between',
18 | padding: `${ spacing.m + spacing.statusBarHeight }px ${ spacing.m }px ${ spacing.m }px`,
19 | },
20 | actions: {
21 | display: 'flex',
22 | alignItems: 'center',
23 | },
24 | icon: {
25 | width: '24px',
26 | marginBottom: '2px',
27 | },
28 | Decrypt: {
29 | background: colors.primaryGradient, // TODO: File issue as this should work
30 | color: colors.bgDark,
31 | },
32 | },
33 | }
34 | }
35 |
36 | componentDidMount = () => {
37 | this.props.showComposer('alias')
38 | }
39 |
40 | counter = 0
41 |
42 | componentDidUpdate = (prevProps) => {
43 | this.counter = this.counter + 1
44 | if (prevProps.alias.privateKeyArmored !== this.props.alias.privateKeyArmored
45 | && this.counter < 3) {
46 | this.props.toggleComposer()
47 | }
48 | }
49 |
50 | render() {
51 | return (
52 |
64 | )
65 | }
66 | }
67 |
68 | export default ReactCSS(Header)
69 |
--------------------------------------------------------------------------------
/app/components/header/HeaderKeyCopy.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import CopyToClipboard from 'react-copy-to-clipboard'
4 |
5 | import { Icon } from '../common/index'
6 |
7 | import colors from '../../assets/styles/variables/colors'
8 |
9 | class HeaderKeyCopy extends Component {
10 |
11 | classes() { // eslint-disable-line
12 | return {
13 | 'default': {
14 | copy: {
15 | cursor: 'pointer',
16 | alignSelf: 'flex-end',
17 | marginTop: '5px',
18 | },
19 | },
20 | }
21 | }
22 |
23 | render() {
24 | return (
25 |
26 |
{
29 | this.props.toggleIsCopied()
30 | setTimeout(() => {
31 | this.props.toggleIsCopied()
32 | }, 2000)
33 | } }
34 | >
35 | { this.props.isCopied ?
36 |
40 | :
41 |
42 |
46 |
47 | }
48 |
49 |
50 | )
51 | }
52 | }
53 |
54 | export default ReactCSS(HeaderKeyCopy)
55 |
--------------------------------------------------------------------------------
/app/components/header/HeaderKeyStatus.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import HeaderKeyCopy from './HeaderKeyCopy'
4 | import HeaderKeyStatusSpinner from './HeaderKeyStatusSpinner'
5 | import HeaderKeyStatusTooltip from './HeaderKeyStatusTooltip'
6 |
7 | class HeaderKeyStatus extends Component {
8 | state = { isShowingTooltip: false }
9 |
10 | setTooltip = (b) => {
11 | this.setState({ isShowingTooltip: b })
12 | }
13 |
14 | handleMouseEnter = () => {
15 | this.setTooltip(true)
16 | }
17 |
18 | handleMouseLeave = () => {
19 | this.setTooltip(false)
20 | }
21 |
22 | render() {
23 | return (
24 |
25 |
29 | { this.props.isGeneratingKey ?
30 |
31 |
32 |
33 | :
34 |
35 |
40 |
41 | }
42 | { this.state.isShowingTooltip ?
43 |
44 |
48 |
49 | :
50 | null
51 | }
52 |
53 |
54 | )
55 | }
56 | }
57 |
58 | export default HeaderKeyStatus
59 |
--------------------------------------------------------------------------------
/app/components/header/HeaderKeyStatusSpinner.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class HeaderKeyStatusSpinner extends Component { // eslint-disable-line
4 |
5 | render() {
6 | return (
7 |
13 | )
14 | }
15 | }
16 |
17 | export default HeaderKeyStatusSpinner
18 |
--------------------------------------------------------------------------------
/app/components/header/HeaderKeyStatusTooltip.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import dynamics from 'dynamics.js'
4 |
5 | import colors from '../../assets/styles/variables/colors'
6 |
7 | class HeaderKeyStatusTooltip extends Component {
8 | classes() { // eslint-disable-line
9 | return {
10 | 'default': {
11 | tooltip: {
12 | position: 'absolute',
13 | display: 'flex',
14 | top: '32px',
15 | right: '50px',
16 | background: colors.bgLight,
17 | borderRadius: '3px',
18 | height: '45%',
19 | padding: '0px 10px',
20 | alignItems: 'center',
21 | boxShadow: '0px 4px 10px 0px rgba(0, 0, 0, 0.33)',
22 |
23 | transform: 'translateY(10px)', // for animation
24 | opacity: '0',
25 | },
26 | arrow: {
27 | position: 'absolute',
28 | right: '-10px',
29 | top: '8px',
30 | width: '0',
31 | height: '0',
32 | borderTop: '10px solid transparent',
33 | borderBottom: '10px solid transparent',
34 | borderLeft: `10px solid ${ colors.bgLight }`,
35 | },
36 | },
37 | }
38 | }
39 |
40 | componentDidMount = () => {
41 | dynamics.animate(this.tooltip, {
42 | translateY: 0,
43 | opacity: 1,
44 | }, {
45 | type: dynamics.spring,
46 | duration: 500,
47 | friction: 400,
48 | })
49 | }
50 |
51 | render() {
52 | return (
53 | (this.tooltip = tooltip) }>
54 |
55 | { this.props.isGeneratingKey ? (
56 |
{ 'Generating Keys: <2min' }
57 | ) : (
58 |
{ this.props.isCopied ? 'Copied!' : 'Copy Public Key' }
59 | ) }
60 |
61 | )
62 | }
63 | }
64 |
65 | export default ReactCSS(HeaderKeyStatusTooltip)
66 |
--------------------------------------------------------------------------------
/app/components/keychain/Keychain.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import KeychainList from './KeychainList'
5 | import KeychainComposerOpener from './KeychainComposerOpener'
6 |
7 | class Keychain extends Component {
8 | classes() { // eslint-disable-line
9 | return {
10 | 'default': {
11 | keychain: {
12 | position: 'fixed',
13 | top: '78px',
14 | left: '0px',
15 | right: '0px',
16 | bottom: '0px',
17 | overflowY: 'scroll', // TODO: elastic scroll
18 | },
19 | },
20 | }
21 | }
22 |
23 | render() {
24 | return (
25 |
36 | )
37 | }
38 | }
39 |
40 | export default ReactCSS(Keychain)
41 |
--------------------------------------------------------------------------------
/app/components/keychain/KeychainComposerOpener.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import dynamics from 'dynamics.js'
4 |
5 | import colors from '../../assets/styles/variables/colors'
6 |
7 | class KeychainComposerOpener extends Component {
8 | classes() { // eslint-disable-line
9 | return {
10 | 'default': {
11 | opener: {
12 | position: 'fixed',
13 | zIndex: '12',
14 | bottom: '-10px',
15 | right: '0px',
16 | left: '0px',
17 | boxShadow: '0 0 14px rgba(0,0,0,0.08), 0 0 4px rgba(0,0,0,0.10)',
18 | height: '55px',
19 | background: colors.bgDark,
20 | padding: '10px',
21 | transform: 'translateY(64px)',
22 | },
23 | button: {
24 | background: '#fff',
25 | height: '45px',
26 | lineHeight: '45px',
27 | borderRadius: '2px',
28 | padding: '0 12px',
29 | color: '#aaa',
30 | },
31 | },
32 | 'isShowingOpener': {
33 | opener: {
34 | transform: 'translateY(0)',
35 | },
36 | },
37 | }
38 | }
39 |
40 | handleClick = () => {
41 | this.props.showComposer('encrypt')
42 | }
43 |
44 | componentWillReceiveProps(nextProps) {
45 | if (!this.props.isShowingOpener && nextProps.isShowingOpener) {
46 | this.moveOpener(0, 'spring', 700, 400)
47 | } else if (this.props.isShowingOpener && !nextProps.isShowingOpener) {
48 | this.moveOpener(64, 'easeInOut', 400, 250)
49 | }
50 | }
51 |
52 | moveOpener(y, style, duration, friction) {
53 | dynamics.animate(this.opener, {
54 | translateY: y,
55 | }, {
56 | type: dynamics[style],
57 | duration,
58 | friction,
59 | })
60 | }
61 |
62 | render() {
63 | return (
64 | (this.opener = opener) }
67 | onClick={ this.handleClick }
68 | >
69 |
Write a Message
70 |
71 | )
72 | }
73 | }
74 |
75 | export default ReactCSS(KeychainComposerOpener)
76 |
--------------------------------------------------------------------------------
/app/components/keychain/KeychainList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import _ from 'lodash'
3 |
4 | import KeychainListItem from './KeychainListItem'
5 |
6 | class KeychainList extends Component {
7 | componentDidMount() { this.props.fetchKeychain() }
8 |
9 | render() {
10 | return (
11 |
12 | { _.map(this.props.keychain, key => (
13 |
22 | )) }
23 |
24 | )
25 | }
26 | }
27 |
28 | export default KeychainList
29 |
--------------------------------------------------------------------------------
/app/components/keychain/KeychainListItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 |
4 | import { User } from '../common/index'
5 |
6 | import colors from '../../assets/styles/variables/colors'
7 | import { spacing, sizing } from '../../assets/styles/variables/utils'
8 |
9 | class KeychainListKey extends Component {
10 | classes() { // eslint-disable-line
11 | return {
12 | 'default': {
13 | item: {
14 | color: '#aaa',
15 | transition: 'color 200ms linear',
16 | },
17 | user: {
18 | padding: `${ spacing.m }px ${ spacing.m }px ${ spacing.m }px`,
19 | display: 'flex',
20 | cursor: 'pointer',
21 | },
22 | spaceLine: {
23 | borderBottom: `solid 1px ${ colors.bgDark }`,
24 | margin: `0px ${ spacing.m }px 0px ${ spacing.m + sizing.avatar + spacing.s }px`,
25 | item: '1',
26 | },
27 | },
28 | 'active': {
29 | item: {
30 | color: '#fff',
31 | },
32 | },
33 | }
34 | }
35 |
36 | handleClick = () => {
37 | this.props.onSelect && this.props.onSelect(this.props.id)
38 | }
39 |
40 | render() {
41 | return (
42 |
52 | )
53 | }
54 | }
55 |
56 | export default ReactCSS(KeychainListKey)
57 |
--------------------------------------------------------------------------------
/app/components/output/Output.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactCSS from 'reactcss'
3 | import CopyToClipboard from 'react-copy-to-clipboard'
4 | import colors from '../../assets/styles/variables/colors'
5 |
6 | import { Icon } from '../common/index'
7 |
8 | class Output extends Component {
9 | state = { copied: false }
10 |
11 | classes() { // eslint-disable-line
12 | return {
13 | 'default': {
14 | wrap: {
15 | position: 'fixed',
16 | top: '0px',
17 | right: '0px',
18 | bottom: '0px',
19 | left: '0px',
20 | zIndex: '9999999',
21 | flexDirection: 'column',
22 | background: colors.bgDark,
23 | display: 'flex',
24 | transform: 'translateY(100%)',
25 | opacity: '0px',
26 | transition: 'all 200ms ease-in-out',
27 | },
28 | head: {
29 | background: '#2B272C',
30 | fontWeight: '300',
31 | padding: '10px 20px',
32 | paddingTop: '35px',
33 | },
34 |
35 | title: {
36 | fontSize: '20px',
37 | },
38 | icon: {
39 | width: '20px',
40 | marginRight: '10px',
41 | display: 'inline-block',
42 | verticalAlign: 'middle',
43 | },
44 | subtitle: {
45 | fontSize: '16px',
46 | color: 'rgba(255,255,255,.7)',
47 | marginTop: '10px',
48 | },
49 | text: {
50 | flex: '1',
51 | position: 'relative',
52 | margin: '0 20px',
53 | marginTop: '-10px',
54 | },
55 | textarea: {
56 | Absolute: '23px auto 10px auto',
57 | border: 'none',
58 | outline: 'none',
59 | width: '100%',
60 | boxSizing: 'border-box',
61 | padding: '10px 3%',
62 | color: '#333',
63 | borderRadius: '2px',
64 | fontSize: '14px',
65 | fontFamily: 'Andale Mono',
66 | marginTop: '5px',
67 | },
68 | actions: {
69 | marginTop: '-18px',
70 | height: '55px',
71 | padding: '20px',
72 | },
73 | confirm: {
74 | color: colors.bgDark,
75 | padding: '10px 20px',
76 | borderRadius: '5px',
77 | backgroundColor: colors.green,
78 | textDecoration: 'none',
79 | cursor: 'pointer',
80 | textAlign: 'center',
81 | display: 'block',
82 | },
83 | cancel: {
84 | color: 'rgba(255,255,255,.4)',
85 | textAlign: 'center',
86 | display: 'block',
87 | marginTop: '7px',
88 | cursor: 'pointer',
89 | },
90 | },
91 | 'isShowingOutput': {
92 | wrap: {
93 | transform: 'translateY(0)',
94 | opacity: '1',
95 | },
96 | },
97 | }
98 | }
99 |
100 | componentWillReceiveProps = () => {
101 | this.state = { copied: false }
102 | }
103 |
104 | handleCancel = () => {
105 | this.props.setOutput('')
106 | this.props.toggleComposer()
107 | }
108 | handleNevermind = () => {
109 | this.props.toggleOutput()
110 | }
111 |
112 | handleKeyDown = (e) => {
113 | (e.keyCode === 27 && this.props.isShowingOutput) && this.handleCancel()
114 | }
115 |
116 |
117 | render() {
118 | return (
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | Success
127 |
128 |
Share this anywhere on the web.
129 |
130 |
131 |
132 |
133 |
134 |
164 |
165 |
166 | )
167 | }
168 | }
169 |
170 | export default ReactCSS(Output)
171 |
--------------------------------------------------------------------------------
/app/config/database.js:
--------------------------------------------------------------------------------
1 | import low from 'lowdb'
2 | import storage from 'lowdb/file-async'
3 | import path from 'path'
4 | import os from 'os'
5 | import underscoreDB from 'underscore-db'
6 |
7 | const dbPath = path.join(os.homedir(), 'felony.json')
8 | const db = low(dbPath, { storage })
9 |
10 | db._.mixin(underscoreDB)
11 |
12 | export default db
13 |
--------------------------------------------------------------------------------
/app/constants/KeychainConstants.js:
--------------------------------------------------------------------------------
1 | export const ADD_KEY = 'ADD_KEY'
2 | export const SET_KEYCHAIN = 'SET_KEYCHAIN'
3 |
--------------------------------------------------------------------------------
/app/constants/UIConstants.js:
--------------------------------------------------------------------------------
1 | export const SELECT_KEY = 'SELECT_KEY'
2 | export const CLEAR_SELECTED_KEYS = 'CLEAR_SELECTED_KEYS'
3 | export const TOGGLE_COMPOSER = 'TOGGLE_COMPOSER'
4 | export const TOGGLE_GENERATING_KEY = 'TOGGLE_GENERATING_KEY'
5 | export const TOGGLE_IS_COPIED = 'TOGGLE_IS_COPIED'
6 | export const SHOW_COMPOSER_WITH_TYPE = 'SHOW_COMPOSER_WITH_TYPE'
7 | export const SET_ACTIVE_ALIAS = 'SET_ACTIVE_ALIAS'
8 | export const SET_OUTPUT = 'SET_OUTPUT'
9 |
--------------------------------------------------------------------------------
/app/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 |
3 | export default class App extends Component { // eslint-disable-line
4 | static propTypes = {
5 | children: PropTypes.element.isRequired,
6 | }
7 |
8 | render() {
9 | return (
10 |
11 | { this.props.children }
12 | {
13 | (() => { // eslint-disable-line consistent-return
14 | if (process.env.NODE_ENV !== 'production') {
15 | const DevTools = require('./DevTools') // eslint-disable-line global-require
16 | return
17 | }
18 | })()
19 | }
20 |
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/containers/ComposerContainer.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { connect } from 'react-redux'
3 | import { toggleComposer, toggleGeneratingKey, addKey, setOutput } from '../actions/index'
4 | import Composer from '../components/composer/Composer'
5 |
6 | const mapStateToProps = (state) => {
7 | const keychain = state.keychainReducer.get('keychain').toJS()
8 | const aliases = _.filter(keychain, (key) => {
9 | return key.privateKeyArmored !== null
10 | })
11 | const first = aliases[0] || null
12 |
13 | return {
14 | isShowingComposer: state.uiReducer.get('isShowingComposer'),
15 | isGeneratingKey: state.uiReducer.get('isGeneratingKey'),
16 | composerType: state.uiReducer.get('composerType'),
17 | selectedKeychain: _.values(_.pick(keychain, (value, key) => {
18 | return state.uiReducer.get('selectedKeychain').toJS()[key]
19 | })),
20 | keychain: _.filter(keychain, (key) => {
21 | return key.privateKeyArmored === null
22 | }),
23 | alias: first,
24 | }
25 | }
26 |
27 | const mapDispatchToProps = (dispatch) => {
28 | return {
29 | toggleComposer: () => {
30 | dispatch(toggleComposer())
31 | },
32 |
33 | toggleGeneratingKey: () => {
34 | dispatch(toggleGeneratingKey())
35 | },
36 |
37 | addKey: (key) => {
38 | dispatch(addKey(key))
39 | },
40 |
41 | setOutput: (output) => {
42 | dispatch(setOutput(output))
43 | },
44 | }
45 | }
46 |
47 | const ComposerContainer = connect(
48 | mapStateToProps,
49 | mapDispatchToProps,
50 | )(Composer)
51 |
52 | export default ComposerContainer
53 |
--------------------------------------------------------------------------------
/app/containers/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createDevTools } from 'redux-devtools'
3 | import LogMonitor from 'redux-devtools-log-monitor'
4 | import DockMonitor from 'redux-devtools-dock-monitor'
5 |
6 | export default createDevTools(
7 |
11 |
12 | ,
13 | )
14 |
--------------------------------------------------------------------------------
/app/containers/FloatingButtonContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { showComposerWithType } from '../actions/index'
3 | import FloatingButton from '../components/floating-button/FloatingButton'
4 |
5 | const mapStateToProps = () => {
6 | return {
7 | }
8 | }
9 |
10 | const mapDispatchToProps = (dispatch) => {
11 | return {
12 | showComposer: (type) => {
13 | dispatch(showComposerWithType(type))
14 | },
15 | }
16 | }
17 |
18 | const FloatingButtonContainer = connect(
19 | mapStateToProps,
20 | mapDispatchToProps,
21 | )(FloatingButton)
22 |
23 | export default FloatingButtonContainer
24 |
--------------------------------------------------------------------------------
/app/containers/HeaderContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import _ from 'lodash'
3 | import { showComposerWithType, toggleIsCopied, toggleComposer } from '../actions/index'
4 | import Header from '../components/header/Header'
5 |
6 | const mapStateToProps = (state) => {
7 | const aliases = _.filter(state.keychainReducer.get('keychain').toJS(), (key) => {
8 | return key.privateKeyArmored !== null
9 | })
10 | const first = aliases[0] || {
11 | name: '',
12 | email: '',
13 | privateKeyArmored: '',
14 | publicKeyArmored: '',
15 | avatar: '',
16 | }
17 |
18 | return {
19 | isCopied: state.uiReducer.get('isCopied'),
20 | isGeneratingKey: state.uiReducer.get('isGeneratingKey'),
21 | alias: first,
22 | }
23 | }
24 |
25 | const mapDispatchToProps = (dispatch) => {
26 | return {
27 | showComposer: (type) => {
28 | dispatch(showComposerWithType(type))
29 | },
30 |
31 | toggleIsCopied: () => {
32 | dispatch(toggleIsCopied())
33 | },
34 |
35 | toggleComposer: () => {
36 | dispatch(toggleComposer())
37 | },
38 | }
39 | }
40 |
41 | const HeaderContainer = connect(
42 | mapStateToProps,
43 | mapDispatchToProps,
44 | )(Header)
45 |
46 | export default HeaderContainer
47 |
--------------------------------------------------------------------------------
/app/containers/KeychainContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import _ from 'lodash'
3 | import { addKey, fetchKeychain, selectKey, showComposerWithType } from '../actions/index'
4 | import Keychain from '../components/keychain/Keychain'
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | // For `KeychainList` component
9 | keychain: _.filter(state.keychainReducer.get('keychain').toJS(), (key) => {
10 | return key.privateKeyArmored === null
11 | }),
12 | selectedKeychain: state.uiReducer.get('selectedKeychain').toJS(),
13 |
14 | // For `KeychainComposerOpener` component
15 | isShowingOpener: _.some(_.values(state.uiReducer.get('selectedKeychain').toJS()), (value) => {
16 | return value
17 | }),
18 | }
19 | }
20 |
21 | const mapDispatchToProps = (dispatch) => {
22 | return {
23 | addKey: (key) => {
24 | dispatch(addKey(key))
25 | },
26 |
27 | fetchKeychain: () => {
28 | dispatch(fetchKeychain())
29 | },
30 |
31 | selectKey: (id) => {
32 | dispatch(selectKey(id))
33 | },
34 |
35 | showComposer: (type) => {
36 | dispatch(showComposerWithType(type))
37 | },
38 | }
39 | }
40 |
41 | const KeychainContainer = connect(
42 | mapStateToProps,
43 | mapDispatchToProps,
44 | )(Keychain)
45 |
46 | export default KeychainContainer
47 |
--------------------------------------------------------------------------------
/app/containers/OutputContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { toggleComposer, setOutput, clearSelectedKeys } from '../actions/index'
3 | import Output from '../components/output/Output'
4 |
5 | const mapStateToProps = (state) => {
6 | const output = state.uiReducer.get('output')
7 | let isShowingOutput = false
8 | if (output.length > 1) {
9 | isShowingOutput = true
10 | }
11 |
12 | return {
13 | output,
14 | isShowingOutput,
15 | composerType: state.uiReducer.get('composerType'),
16 | }
17 | }
18 |
19 | const mapDispatchToProps = (dispatch) => {
20 | return {
21 | toggleComposer: () => {
22 | dispatch(toggleComposer())
23 | },
24 |
25 | clearSelectedKeys: () => {
26 | dispatch(clearSelectedKeys())
27 | },
28 |
29 | setOutput: (output) => {
30 | dispatch(setOutput(output))
31 | },
32 | }
33 | }
34 |
35 | const OutputContainer = connect(
36 | mapStateToProps,
37 | mapDispatchToProps,
38 | )(Output)
39 |
40 | export default OutputContainer
41 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { createStore, applyMiddleware } from 'redux'
5 |
6 | import thunk from 'redux-thunk'
7 | import rootReducer from './reducers/index'
8 |
9 | import Felony from './components/Felony'
10 |
11 | const store = createStore(rootReducer, applyMiddleware(thunk))
12 |
13 | render(
14 |
15 |
16 | ,
17 | document.getElementById('root'),
18 | )
19 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "felony",
3 | "productName": "Felony",
4 | "version": "0.10.3",
5 | "dependencies": {
6 | "openpgp": "^2.2.1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import uiReducer from './uiReducer'
3 | import keychainReducer from './keychainReducer'
4 |
5 | const rootReducer = combineReducers({
6 | uiReducer,
7 | keychainReducer,
8 | })
9 |
10 | export default rootReducer
11 |
--------------------------------------------------------------------------------
/app/reducers/keychainReducer.js:
--------------------------------------------------------------------------------
1 | import { OrderedMap, Record } from 'immutable'
2 | import { ADD_KEY, SET_KEYCHAIN } from '../constants/KeychainConstants'
3 |
4 | const KeyRecord = Record({
5 | id: '',
6 | name: '',
7 | email: '',
8 | publicKeyArmored: '',
9 | privateKeyArmored: '',
10 | avatar: '',
11 | completed: false,
12 | })
13 |
14 | const initialState = OrderedMap({
15 | keychain: OrderedMap({}),
16 | })
17 |
18 | const keychainReducer = (state = initialState, action) => {
19 | switch (action.type) {
20 | case ADD_KEY:
21 | return state.setIn(
22 | ['keychain', action.key.id],
23 | new KeyRecord({
24 | name: action.key.name,
25 | id: action.key.id,
26 | email: action.key.email,
27 | publicKeyArmored: action.key.publicKeyArmored,
28 | privateKeyArmored: action.key.privateKeyArmored || null,
29 | avatar: action.key.avatar,
30 | }),
31 | )
32 | case SET_KEYCHAIN:
33 | return state.withMutations((st) => {
34 | for (const key of action.keychain) { // eslint-disable-line
35 | st.setIn(
36 | ['keychain', key.id],
37 | new KeyRecord({
38 | name: key.name,
39 | id: key.id,
40 | email: key.email,
41 | publicKeyArmored: key.publicKeyArmored,
42 | privateKeyArmored: key.privateKeyArmored || null,
43 | avatar: key.avatar,
44 | }),
45 | )
46 | }
47 | })
48 |
49 | default:
50 | return state
51 | }
52 | }
53 |
54 | export default keychainReducer
55 |
--------------------------------------------------------------------------------
/app/reducers/uiReducer.js:
--------------------------------------------------------------------------------
1 | import { OrderedMap } from 'immutable'
2 | import { TOGGLE_COMPOSER, TOGGLE_GENERATING_KEY, TOGGLE_IS_COPIED,
3 | SHOW_COMPOSER_WITH_TYPE, SELECT_KEY, CLEAR_SELECTED_KEYS,
4 | SET_ACTIVE_ALIAS, SET_OUTPUT } from '../constants/UIConstants'
5 |
6 | const initialState = OrderedMap({
7 | isShowingComposer: false,
8 | composerType: '',
9 | selectedKeychain: OrderedMap({}),
10 | activeAlias: '',
11 | output: '',
12 | isGeneratingKey: false,
13 | isCopied: false,
14 | })
15 |
16 | const uiReducer = (state = initialState, action) => {
17 | switch (action.type) {
18 | case TOGGLE_COMPOSER:
19 | return state.set('isShowingComposer', !state.get('isShowingComposer'))
20 | case TOGGLE_GENERATING_KEY:
21 | return state.set('isGeneratingKey', !state.get('isGeneratingKey'))
22 | case TOGGLE_IS_COPIED:
23 | return state.set('isCopied', !state.get('isCopied'))
24 | case SHOW_COMPOSER_WITH_TYPE:
25 | return state.set('composerType', action.data).set('isShowingComposer', true)
26 | case SELECT_KEY: {
27 | if (state.hasIn(['selectedKeychain', action.id])
28 | && state.getIn(['selectedKeychain', action.id]) === true) {
29 | return state.setIn(['selectedKeychain', action.id], false)
30 | }
31 | return state.setIn(['selectedKeychain', action.id], true)
32 | }
33 | case CLEAR_SELECTED_KEYS:
34 | return state.setIn(['selectedKeychain'], OrderedMap({}))
35 |
36 | case SET_ACTIVE_ALIAS:
37 | return state.set('activeAlias', action.activeAlias)
38 |
39 | case SET_OUTPUT:
40 | return state.set('output', action.output)
41 |
42 | default:
43 | return state
44 | }
45 | }
46 |
47 | export default uiReducer
48 |
--------------------------------------------------------------------------------
/app/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, IndexRoute } from 'react-router'
3 | import App from './containers/App'
4 | import Felony from './components/Felony'
5 |
6 | export default (
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/app/utils/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henryboldi/felony/8e948160125e3fb69ff1992f03ae0e246577aca7/app/utils/.gitkeep
--------------------------------------------------------------------------------
/app/utils/icons.js:
--------------------------------------------------------------------------------
1 | /* eslint max-len: 0 */
2 |
3 | const store = {
4 | 'more': ``,
7 | 'add-key': ``,
10 | 'verify': ``,
13 | 'sign': ``, // materialdesignicons.com pencil
16 | 'decrypt': ``,
19 | 'encrypt': ``, // materialdesignicons.com lock
22 | 'plus': ``, // materialdesignicons.com plus
25 | 'copy': ``,
28 | 'check': ``,
31 | }
32 |
33 | export const getIcon = (name) => { // eslint-disable-line import/prefer-default-export
34 | return {}.hasOwnProperty.call(store, name) ? store[name] : 'n/a'
35 | }
36 |
--------------------------------------------------------------------------------
/app/utils/pgp.js:
--------------------------------------------------------------------------------
1 | const openpgp = require('openpgp')
2 | openpgp.initWorker({ path: '../node_modules/openpgp/dist/openpgp.worker.min.js' })
3 | openpgp.config.aead_protect = true
4 | openpgp.config.use_native = true
5 |
6 | import keytar from 'keytar'
7 |
8 | export function getPublicKeysFromKeychain(keychain) {
9 | return keychain.map(key => key.publicKeyArmored)
10 | }
11 |
12 | function applyFelonyBranding(openpgpBrandedKey) {
13 | let output
14 | output = openpgpBrandedKey.replace(/Version: OpenPGP\.js [^<]\d\.\d\.\d/g,
15 | 'Version: 🔑 Felony (PGP made easy) v0.0.1')
16 | output = output.replace('Comment: http://openpgpjs.org', 'Comment: How do I use this? https://github.com/henryboldi/felony')
17 | return output
18 | }
19 |
20 | function massagePGP(string) {
21 | return string.replace('\n', '\r\n')
22 | .replace('===', '==\r\n=')
23 | }
24 |
25 | function getNameString(key) {
26 | const userStr = key.users[0].userId.userid
27 | const email = userStr.substring(userStr.lastIndexOf('<') + 1, userStr.lastIndexOf('>'))
28 | const name = userStr.substring(0, userStr.lastIndexOf(' '))
29 | return `${ name } <${ email }>`
30 | }
31 |
32 | async function decryptPrivateKey(privateKey, passphrase) {
33 | const unlocked = await openpgp.decryptKey({
34 | privateKey,
35 | passphrase,
36 | })
37 | const decryptedKey = unlocked.key
38 | return decryptedKey
39 | }
40 |
41 | function getPrivateKeyPassphrase(privateKey) {
42 | const nameString = getNameString(privateKey)
43 | const passphrase = keytar.getPassword('felony', nameString)
44 | return passphrase
45 | }
46 |
47 | export async function generateKey({ name, email = '' }, passphrase) {
48 | const key = await openpgp.generateKey({
49 | userIds: [{ name, email }],
50 | numBits: 4096,
51 | passphrase,
52 | })
53 |
54 | return {
55 | name,
56 | email,
57 | privateKeyArmored: applyFelonyBranding(key.privateKeyArmored),
58 | publicKeyArmored: applyFelonyBranding(key.publicKeyArmored),
59 | }
60 | }
61 |
62 | export async function readArmored(armoredKey) {
63 | const massagedKey = massagePGP(armoredKey)
64 | const key = await openpgp.key.readArmored(massagedKey)
65 | const userStr = key.keys[0].users[0].userId.userid
66 | const email = userStr.substring(userStr.lastIndexOf('<') + 1, userStr.lastIndexOf('>'))
67 | const name = userStr.substring(0, userStr.lastIndexOf(' '))
68 |
69 | return {
70 | name,
71 | email,
72 | publicKeyArmored: massagedKey,
73 | }
74 | }
75 |
76 | export async function encrypt(message, selectedKeys, privateKeyArmored) {
77 | const publicKeysArmored = getPublicKeysFromKeychain(selectedKeys)
78 | const publicKeys = []
79 | for (const key of publicKeysArmored) {
80 | publicKeys.push(openpgp.key.readArmored(key).keys[0])
81 | }
82 |
83 | const privateKey = openpgp.key.readArmored(privateKeyArmored).keys[0]
84 | const passphrase = getPrivateKeyPassphrase(privateKey)
85 | privateKey.decrypt(passphrase)
86 |
87 | const options = {
88 | data: message,
89 | publicKeys,
90 | privateKeys: [privateKey],
91 | }
92 |
93 | const ciphertext = await openpgp.encrypt(options)
94 | const encryptedMessage = ciphertext.data
95 |
96 | return applyFelonyBranding(encryptedMessage)
97 | }
98 |
99 | export async function decrypt(encryptedMessage, privateKeyArmored) {
100 | const encryptedMessageMassaged = massagePGP(encryptedMessage)
101 | const privateKey = openpgp.key.readArmored(privateKeyArmored).keys[0]
102 | const passphrase = getPrivateKeyPassphrase(privateKey)
103 |
104 | privateKey.decrypt(passphrase)
105 |
106 | const options = {
107 | message: openpgp.message.readArmored(encryptedMessageMassaged),
108 | privateKey,
109 | }
110 | const plaintext = await openpgp.decrypt(options)
111 |
112 | return plaintext.data
113 | }
114 |
115 | export async function sign(message, privateKeyArmored) {
116 | const privateKey = openpgp.key.readArmored(privateKeyArmored).keys[0]
117 | const passphrase = await getPrivateKeyPassphrase(privateKey)
118 | const decryptedKey = await decryptPrivateKey(privateKey, passphrase)
119 |
120 | const options = {
121 | data: message,
122 | privateKeys: [decryptedKey],
123 | }
124 |
125 | const signed = await openpgp.sign(options)
126 |
127 | return applyFelonyBranding(signed.data)
128 | }
129 |
130 | export async function verify(signedMessage, keychain) {
131 | const signedMessageMassaged = massagePGP(signedMessage)
132 | const options = {
133 | message: openpgp.cleartext.readArmored(signedMessageMassaged),
134 | }
135 |
136 | let match = false
137 | for (const key of keychain) {
138 | options.publicKeys = openpgp.key.readArmored(key.publicKeyArmored).keys
139 | const verified = await openpgp.verify(options)
140 | if (verified.signatures[0].valid === true) {
141 | match = key
142 | }
143 | }
144 |
145 | return match
146 | }
147 |
--------------------------------------------------------------------------------
/main.development.js:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, Menu, shell, autoUpdater } from 'electron' // eslint-disable-line
2 | import open from 'open'
3 |
4 | const { version } = require('./package.json')
5 |
6 | let menu
7 | let template
8 | let mainWindow = null
9 |
10 | const feedUrl = `https://felony-app-update.herokuapp.com/update/${ process.platform }_${ process.arch }/${ version }`
11 |
12 | if (process.env.NODE_ENV !== 'development') {
13 | autoUpdater.setFeedURL(feedUrl)
14 |
15 | try {
16 | let check = true
17 |
18 | autoUpdater.checkForUpdates()
19 | setInterval(() => {
20 | if (check) {
21 | autoUpdater.checkForUpdates()
22 | }
23 | }, 120000)
24 |
25 | autoUpdater.on('checking-for-update', () => {
26 | check = false
27 | })
28 |
29 | autoUpdater.on('update-available', () => {
30 | // Update
31 | })
32 |
33 | autoUpdater.on('update-not-available', () => {
34 | // No update
35 | })
36 |
37 | autoUpdater.on('update-downloaded', () => {
38 | // TODO: ask before installing
39 | autoUpdater.quitAndInstall()
40 | })
41 | } catch (err) {
42 | console.log('error', err) // eslint-disable-line no-console
43 | }
44 | }
45 |
46 |
47 | const createWindow = () => {
48 | mainWindow = new BrowserWindow({
49 | 'show': false,
50 | 'resizable': false,
51 | 'width': 295,
52 | 'height': 435,
53 | 'titleBarStyle': 'hidden',
54 | })
55 |
56 | mainWindow.loadURL(`file://${ __dirname }/app/app.html`)
57 |
58 | mainWindow.webContents.on('did-finish-load', () => {
59 | mainWindow.show()
60 | mainWindow.focus()
61 | })
62 |
63 | mainWindow.webContents.on('new-window', (event, url) => {
64 | event.preventDefault()
65 | open(url)
66 | })
67 | mainWindow.on('closed', () => {
68 | mainWindow = null
69 | })
70 |
71 | if (process.env.NODE_ENV === 'development') {
72 | mainWindow.openDevTools()
73 | }
74 |
75 | if (process.platform === 'darwin') {
76 | template = [{
77 | label: 'Electron',
78 | submenu: [{
79 | label: 'About Felony',
80 | selector: 'orderFrontStandardAboutPanel:',
81 | }, {
82 | type: 'separator',
83 | }, {
84 | label: 'Services',
85 | submenu: [],
86 | }, {
87 | type: 'separator',
88 | }, {
89 | label: 'Hide Felony',
90 | accelerator: 'Command+H',
91 | selector: 'hide:',
92 | }, {
93 | label: 'Hide Others',
94 | accelerator: 'Command+Shift+H',
95 | selector: 'hideOtherApplications:',
96 | }, {
97 | label: 'Show All',
98 | selector: 'unhideAllApplications:',
99 | }, {
100 | type: 'separator',
101 | }, {
102 | label: 'Quit',
103 | accelerator: 'Command+Q',
104 | click() {
105 | app.quit()
106 | },
107 | }],
108 | }, {
109 | label: 'Edit',
110 | submenu: [{
111 | label: 'Undo',
112 | accelerator: 'Command+Z',
113 | selector: 'undo:',
114 | }, {
115 | label: 'Redo',
116 | accelerator: 'Shift+Command+Z',
117 | selector: 'redo:',
118 | }, {
119 | type: 'separator',
120 | }, {
121 | label: 'Cut',
122 | accelerator: 'Command+X',
123 | selector: 'cut:',
124 | }, {
125 | label: 'Copy',
126 | accelerator: 'Command+C',
127 | selector: 'copy:',
128 | }, {
129 | label: 'Paste',
130 | accelerator: 'Command+V',
131 | selector: 'paste:',
132 | }, {
133 | label: 'Select All',
134 | accelerator: 'Command+A',
135 | selector: 'selectAll:',
136 | }],
137 | }, {
138 | label: 'View',
139 | submenu: (process.env.NODE_ENV === 'development') ? [{
140 | label: 'Reload',
141 | accelerator: 'Command+R',
142 | click() {
143 | mainWindow.restart()
144 | },
145 | }, {
146 | label: 'Toggle Full Screen',
147 | accelerator: 'Ctrl+Command+F',
148 | click() {
149 | if (mainWindow) {
150 | mainWindow.setFullScreen(!mainWindow.isFullScreen())
151 | }
152 | },
153 | }, {
154 | label: 'Toggle Developer Tools',
155 | accelerator: 'Alt+Command+I',
156 | click() {
157 | mainWindow.toggleDevTools()
158 | },
159 | }] : [{
160 | label: 'Toggle Full Screen',
161 | accelerator: 'Ctrl+Command+F',
162 | click() {
163 | if (mainWindow) {
164 | mainWindow.setFullScreen(!mainWindow.isFullScreen())
165 | }
166 | },
167 | }],
168 | }, {
169 | label: 'Window',
170 | submenu: [{
171 | label: 'Minimize',
172 | accelerator: 'Command+M',
173 | selector: 'performMiniaturize:',
174 | }, {
175 | label: 'Close',
176 | accelerator: 'Command+W',
177 | selector: 'performClose:',
178 | }, {
179 | type: 'separator',
180 | }, {
181 | label: 'Bring All to Front',
182 | selector: 'arrangeInFront:',
183 | }],
184 | }, {
185 | label: 'Help',
186 | submenu: [{
187 | label: 'Learn More',
188 | click() {
189 | shell.openExternal('http://electron.atom.io')
190 | },
191 | }, {
192 | label: 'Documentation',
193 | click() {
194 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme')
195 | },
196 | }, {
197 | label: 'Community Discussions',
198 | click() {
199 | shell.openExternal('https://discuss.atom.io/c/electron')
200 | },
201 | }, {
202 | label: 'Search Issues',
203 | click() {
204 | shell.openExternal('https://github.com/atom/electron/issues')
205 | },
206 | }],
207 | }]
208 |
209 | menu = Menu.buildFromTemplate(template)
210 | Menu.setApplicationMenu(menu)
211 | } else {
212 | template = [{
213 | label: '&File',
214 | submenu: [{
215 | label: '&Open',
216 | accelerator: 'Ctrl+O',
217 | }, {
218 | label: '&Close',
219 | accelerator: 'Ctrl+W',
220 | click() {
221 | mainWindow.close()
222 | },
223 | }],
224 | }, {
225 | label: '&View',
226 | submenu: (process.env.NODE_ENV === 'development') ? [{
227 | label: '&Reload',
228 | accelerator: 'Ctrl+R',
229 | click() {
230 | mainWindow.restart()
231 | },
232 | }, {
233 | label: 'Toggle &Full Screen',
234 | accelerator: 'F11',
235 | click() {
236 | if (mainWindow) {
237 | mainWindow.setFullScreen(!mainWindow.isFullScreen())
238 | }
239 | },
240 | }, {
241 | label: 'Toggle &Developer Tools',
242 | accelerator: 'Alt+Ctrl+I',
243 | click() {
244 | mainWindow.toggleDevTools()
245 | },
246 | }] : [{
247 | label: 'Toggle &Full Screen',
248 | accelerator: 'F11',
249 | click() {
250 | if (mainWindow) {
251 | mainWindow.setFullScreen(!mainWindow.isFullScreen())
252 | }
253 | },
254 | }],
255 | }, {
256 | label: 'Help',
257 | submenu: [{
258 | label: 'Learn More',
259 | click() {
260 | shell.openExternal('http://electron.atom.io')
261 | },
262 | }, {
263 | label: 'Documentation',
264 | click() {
265 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme')
266 | },
267 | }, {
268 | label: 'Community Discussions',
269 | click() {
270 | shell.openExternal('https://discuss.atom.io/c/electron')
271 | },
272 | }, {
273 | label: 'Search Issues',
274 | click() {
275 | shell.openExternal('https://github.com/atom/electron/issues')
276 | },
277 | }],
278 | }]
279 | menu = Menu.buildFromTemplate(template)
280 | mainWindow.setMenu(menu)
281 | }
282 | }
283 |
284 | if (process.env.NODE_ENV === 'development') {
285 | require('electron-debug')() // eslint-disable-line global-require
286 | }
287 |
288 | app.on('window-all-closed', () => {
289 | if (process.platform !== 'darwin') app.quit()
290 | })
291 |
292 | app.on('ready', createWindow)
293 | app.on('activate', () => {
294 | if (mainWindow === null) {
295 | createWindow()
296 | }
297 | })
298 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import 'babel-polyfill'
3 | import os from 'os'
4 | import webpack from 'webpack'
5 | import packager from 'electron-packager'
6 | import electronInstaller from 'electron-winstaller'
7 | import del from 'del'
8 | import { exec } from 'child_process'
9 | import electronCfg from './webpack.config.electron'
10 | import cfg from './webpack.config.production'
11 | import pkg from './package.json'
12 |
13 | const argv = require('minimist')(process.argv.slice(2))
14 |
15 | const deps = Object.keys(pkg.dependencies)
16 | const devDeps = Object.keys(pkg.devDependencies)
17 | const appName = argv.name || argv.n || pkg.productName
18 | const shouldUseAsar = argv.asar || argv.a || false
19 | const shouldBuildAll = argv.all || false
20 |
21 | const DEFAULT_OPTS = {
22 | dir: './',
23 | name: appName,
24 | asar: shouldUseAsar,
25 | ignore: [
26 | '/test($|/)',
27 | '/tools($|/)',
28 | '/release($|/)',
29 | '/main.development.js',
30 | ].concat(devDeps.map(name => `/node_modules/${ name }($|/)`))
31 | .concat(
32 | deps.filter(name => !electronCfg.default.externals.includes(name))
33 | .map(name => `/node_modules/${ name }($|/)`),
34 | ),
35 | }
36 |
37 | const icon = argv.icon || argv.i || 'app/app'
38 |
39 | if (icon) {
40 | DEFAULT_OPTS.icon = icon
41 | }
42 |
43 | const version = argv.version || argv.v
44 |
45 | if (version) {
46 | DEFAULT_OPTS.version = version
47 | startPack()
48 | } else {
49 | // use the same version as the currently-installed electron-prebuilt
50 | exec('npm list electron-prebuilt --dev', (err, stdout) => {
51 | if (err) {
52 | DEFAULT_OPTS.version = '1.2.6'
53 | } else {
54 | DEFAULT_OPTS.version = stdout.split('electron-prebuilt@')[1].replace(/\s/g, '')
55 | }
56 |
57 | startPack()
58 | })
59 | }
60 |
61 |
62 | function build(cfg) {
63 | return new Promise((resolve, reject) => {
64 | webpack(cfg, (err, stats) => {
65 | if (err) return reject(err)
66 | resolve(stats)
67 | })
68 | })
69 | }
70 |
71 | function startPack() {
72 | console.log('start pack...')
73 | build(electronCfg.default)
74 | .then(() => build(cfg.default))
75 | .then(() => del('release'))
76 | .then(() => {
77 | if (shouldBuildAll) {
78 | // build for all platforms
79 | const archs = ['ia32', 'x64']
80 | const platforms = ['linux', 'win32', 'darwin']
81 |
82 | platforms.forEach((plat) => {
83 | archs.forEach((arch) => {
84 | pack(plat, arch, log(plat, arch))
85 | })
86 | })
87 | } else {
88 | // build for current platform only
89 | pack(os.platform(), os.arch(), log(os.platform(), os.arch()))
90 | }
91 | })
92 | .catch((err) => {
93 | console.error(err)
94 | })
95 | }
96 |
97 | function pack(plat, arch, cb) {
98 | // there is no darwin ia32 electron
99 | if (plat === 'darwin' && arch === 'ia32') return
100 |
101 | const iconObj = {
102 | icon: DEFAULT_OPTS.icon + (() => {
103 | let extension = '.png'
104 | if (plat === 'darwin') {
105 | extension = '.icns'
106 | } else if (plat === 'win32') {
107 | extension = '.ico'
108 | }
109 | return extension
110 | })(),
111 | }
112 |
113 | const opts = Object.assign({}, DEFAULT_OPTS, iconObj, {
114 | 'platform': plat,
115 | arch,
116 | 'prune': true,
117 | 'app-version': pkg.version || DEFAULT_OPTS.version,
118 | 'out': `release/${ plat }-${ arch }`,
119 | })
120 |
121 | if (`${ plat }-${ arch }` === 'darwin-x64') {
122 | opts['osx-sign'] = {
123 | app: '/release/darwin-x64/Felony-darwin-x64/Felony.app',
124 | identity: 'xxxxxxxxx', // Developer ID Application: * (*)
125 | }
126 | }
127 |
128 | console.log('opts', opts)
129 |
130 | packager(opts, cb)
131 | }
132 |
133 | function log(plat, arch) {
134 | return (err) => {
135 | if (err) return console.error(err)
136 | console.log(`${ plat }-${ arch } finished`)
137 | if (`${ plat }-${ arch }` === 'win32-x64') {
138 | const resultPromise = electronInstaller.createWindowsInstaller({
139 | appDirectory: 'release/win32-x64/Felony-win32-x64',
140 | outputDirectory: 'release/win32-x64-installer',
141 | authors: 'Henry Boldizsar',
142 | exe: 'Felony.exe',
143 | })
144 | resultPromise.then(() => console.log('It worked!'), e => console.log(`No dice: ${ e.message }`))
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "felony",
3 | "productName": "Felony",
4 | "version": "0.10.3",
5 | "author": {
6 | "name": "Henry Boldizsar",
7 | "email": "henrymb67@gmail.com",
8 | "url": "https://github.com/henryboldi"
9 | },
10 | "description": "Felony makes sending encrypted messages anywhere easy.",
11 | "main": "main.js",
12 | "scripts": {
13 | "test": "cross-env NODE_ENV=test mocha --compilers js:babel-register --recursive --require ./test/setup.js test/**/*.spec.js",
14 | "test-watch": "npm test -- --watch",
15 | "test-e2e": "cross-env NODE_ENV=test mocha --compilers js:babel-register --require ./test/setup.js --require co-mocha ./test/e2e.js",
16 | "lint": "eslint app test *.js",
17 | "hot-server": "node -r babel-register server.js",
18 | "build-main": "cross-env NODE_ENV=production node -r babel-register ./node_modules/.bin/webpack --config webpack.config.electron.js --progress --profile --colors",
19 | "build-renderer": "cross-env NODE_ENV=production node -r babel-register ./node_modules/.bin/webpack --config webpack.config.production.js --progress --profile --colors",
20 | "build": "npm run build-main && npm run build-renderer",
21 | "start": "cross-env NODE_ENV=production electron ./",
22 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./main.development",
23 | "package": "cross-env NODE_ENV=production node -r babel-register package.js",
24 | "package-all": "npm run package -- --all",
25 | "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
26 | "dev": "concurrently --kill-others \"npm run hot-server\" \"npm run start-hot\""
27 | },
28 | "bin": {
29 | "electron": "./node_modules/.bin/electron"
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/henryboldi/felony.git"
34 | },
35 | "license": "MIT",
36 | "bugs": {
37 | "url": "https://github.com/henryboldi/felony/issues"
38 | },
39 | "keywords": [
40 | "electron",
41 | "boilerplate",
42 | "react",
43 | "react-router",
44 | "flux",
45 | "webpack",
46 | "react-hot"
47 | ],
48 | "homepage": "https://github.com/henryboldi/felony#readme",
49 | "devDependencies": {
50 | "asar": "^0.11.0",
51 | "babel-core": "^6.7.6",
52 | "babel-eslint": "^6.1.2",
53 | "babel-loader": "^6.2.4",
54 | "babel-plugin-add-module-exports": "^0.1.2",
55 | "babel-plugin-webpack-loaders": "^0.4.0",
56 | "babel-polyfill": "^6.7.4",
57 | "babel-preset-es2015": "^6.6.0",
58 | "babel-preset-es2015-rollup": "^1.1.1",
59 | "babel-preset-react": "^6.5.0",
60 | "babel-preset-react-hmre": "^1.1.1",
61 | "babel-preset-stage-0": "^6.5.0",
62 | "babel-register": "^6.7.2",
63 | "chai": "^3.5.0",
64 | "chromedriver": "2.21.2",
65 | "co-mocha": "^1.1.2",
66 | "concurrently": "^2.0.0",
67 | "cross-env": "^1.0.7",
68 | "css-loader": "^0.23.1",
69 | "del": "^2.2.0",
70 | "electron-packager": "^7.0.3",
71 | "electron-prebuilt": "^1.2.6",
72 | "electron-rebuild": "^1.1.3",
73 | "electron-winstaller": "^2.3.1",
74 | "eslint": "^3.10.0",
75 | "eslint-config-airbnb": "^13.0.0",
76 | "eslint-plugin-import": "^2.2.0",
77 | "eslint-plugin-jsx-a11y": "^2.2.3",
78 | "eslint-plugin-react": "^6.6.0",
79 | "express": "^4.13.4",
80 | "extract-text-webpack-plugin": "^1.0.1",
81 | "fbjs-scripts": "^0.6.0",
82 | "file-loader": "^0.8.5",
83 | "jsdom": "^8.4.0",
84 | "json-loader": "^0.5.4",
85 | "minimist": "^1.2.0",
86 | "mocha": "^2.4.5",
87 | "node-libs-browser": "^1.0.0",
88 | "react-addons-test-utils": "^15.0.1",
89 | "redux-devtools": "^3.2.0",
90 | "redux-devtools-dock-monitor": "^1.1.1",
91 | "redux-devtools-log-monitor": "^1.0.11",
92 | "redux-logger": "^2.6.1",
93 | "rollup-plugin-babel": "^2.5.1",
94 | "selenium-webdriver": "^2.53.1",
95 | "sinon": "^1.17.3",
96 | "style-loader": "^0.13.1",
97 | "webpack": "^1.13.0",
98 | "webpack-dev-middleware": "^1.6.1",
99 | "webpack-hot-middleware": "^2.10.0",
100 | "webpack-node-externals": "^1.2.0"
101 | },
102 | "dependencies": {
103 | "babel-plugin-transform-runtime": "^6.9.0",
104 | "body-parser": "^1.15.0",
105 | "classnames": "^2.2.3",
106 | "css-modules-require-hook": "^4.0.0",
107 | "dynamics.js": "^0.0.9",
108 | "electron-debug": "^0.6.0",
109 | "electron-log": "^1.0.17",
110 | "font-awesome": "^4.6.1",
111 | "immutable": "^3.7.6",
112 | "isomorphic-fetch": "^2.2.1",
113 | "keytar": "^3.0.2",
114 | "lodash": "^3.10.1",
115 | "lowdb": "^0.12.4",
116 | "merge": "^1.2.0",
117 | "node-notifier": "^4.6.0",
118 | "normalize.css": "^3.0.3",
119 | "open": "0.0.5",
120 | "openpgp": "^2.2.1",
121 | "os": "^0.1.0",
122 | "react": "^15.0.2",
123 | "react-copy-to-clipboard": "^4.1.0",
124 | "react-dom": "^15.0.1",
125 | "react-ladda": "^4.0.3",
126 | "react-redux": "^4.4.5",
127 | "react-router": "^2.2.4",
128 | "react-router-redux": "^4.0.2",
129 | "reactcss": "https://github.com/casesandberg/reactcss/tarball/1.0.0-perf",
130 | "redux": "^3.4.0",
131 | "redux-thunk": "^2.0.1",
132 | "source-map-support": "^0.4.0",
133 | "underscore-db": "^0.9.1",
134 | "url-loader": "^0.5.7",
135 | "uuid": "^2.0.1"
136 | },
137 | "devEngines": {
138 | "node": ">=4.x",
139 | "npm": ">=2.x"
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import express from 'express'
3 | import webpack from 'webpack'
4 | import webpackDevMiddleware from 'webpack-dev-middleware'
5 | import webpackHotMiddleware from 'webpack-hot-middleware'
6 |
7 | import config from './webpack.config.development'
8 |
9 | const app = express()
10 | const compiler = webpack(config)
11 | const PORT = 3000
12 |
13 | app.use(webpackDevMiddleware(compiler, {
14 | publicPath: config.output.publicPath,
15 | stats: {
16 | colors: true,
17 | },
18 | }))
19 |
20 | app.use(webpackHotMiddleware(compiler))
21 |
22 | app.listen(PORT, 'localhost', (err) => {
23 | if (err) {
24 | console.error(err)
25 | return
26 | }
27 |
28 | console.log(`Listening at http://localhost:${ PORT }`)
29 | })
30 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | export default {
4 | module: {
5 | loaders: [{
6 | test: /\.jsx?$/,
7 | loaders: ['babel-loader'],
8 | exclude: /node_modules/,
9 | }, {
10 | test: /\.json$/,
11 | loader: 'json-loader',
12 | },
13 | {
14 | test: /\.(png|woff|woff2|eot|ttf|svg)$/,
15 | loader: 'url-loader?limit=100000',
16 | },
17 | ],
18 | },
19 | output: {
20 | path: path.join(__dirname, 'dist'),
21 | filename: 'bundle.js',
22 | libraryTarget: 'commonjs2',
23 | },
24 | resolve: {
25 | extensions: ['', '.js', '.jsx'],
26 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
27 | },
28 | plugins: [
29 |
30 | ],
31 | externals: [
32 |
33 | // put your node 3rd party libraries which can't be built with webpack here
34 | // (mysql, mongodb, and so on..)
35 | ],
36 | }
37 |
--------------------------------------------------------------------------------
/webpack.config.development.js:
--------------------------------------------------------------------------------
1 | /* eslint max-len: 0 */
2 | import webpack from 'webpack'
3 | import baseConfig from './webpack.config.base'
4 | import nodeExternals from 'webpack-node-externals'
5 |
6 | const config = {
7 | ...baseConfig,
8 |
9 | debug: true,
10 |
11 | devtool: 'cheap-module-eval-source-map',
12 |
13 | entry: [
14 | 'babel-polyfill',
15 | 'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr',
16 | './app/index',
17 | ],
18 |
19 | output: {
20 | ...baseConfig.output,
21 | publicPath: 'http://localhost:3000/dist/',
22 | },
23 |
24 | module: {
25 | ...baseConfig.module,
26 | loaders: [
27 | ...baseConfig.module.loaders,
28 |
29 | {
30 | test: /\.global\.css$/,
31 | loaders: [
32 | 'style-loader',
33 | 'css-loader',
34 | ],
35 | },
36 |
37 | {
38 | test: /^((?!\.global).)*\.css$/,
39 | loaders: [
40 | 'style-loader',
41 | 'css-loader',
42 | ],
43 | },
44 | ],
45 | },
46 |
47 | plugins: [
48 | ...baseConfig.plugins,
49 | new webpack.HotModuleReplacementPlugin(),
50 | new webpack.NoErrorsPlugin(),
51 | new webpack.DefinePlugin({
52 | '__DEV__': true,
53 | 'process.env': {
54 | NODE_ENV: JSON.stringify('development'),
55 | },
56 | }),
57 | ],
58 |
59 | target: 'electron-renderer',
60 |
61 | externals: [
62 | nodeExternals({
63 | whitelist: ['webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr', 'normalize.css'],
64 | }),
65 |
66 | // put your node 3rd party libraries which can't be built with webpack here
67 | // (mysql, mongodb, and so on..)
68 | ],
69 | }
70 |
71 | export default config
72 |
--------------------------------------------------------------------------------
/webpack.config.electron.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack'
2 | import baseConfig from './webpack.config.base'
3 |
4 | export default {
5 | ...baseConfig,
6 |
7 | devtool: 'source-map',
8 |
9 | entry: [
10 | './main.development',
11 | ],
12 |
13 | output: {
14 | path: __dirname,
15 | filename: './main.js',
16 | },
17 |
18 | plugins: [
19 | new webpack.optimize.UglifyJsPlugin({
20 | compressor: {
21 | warnings: false,
22 | },
23 | }),
24 | new webpack.BannerPlugin(
25 | 'require("source-map-support").install();',
26 | { raw: true, entryOnly: false }
27 | ),
28 | new webpack.DefinePlugin({
29 | 'process.env': {
30 | NODE_ENV: JSON.stringify('production'),
31 | },
32 | }),
33 | ],
34 |
35 | target: 'electron-renderer',
36 |
37 | node: {
38 | __dirname: false,
39 | __filename: false,
40 | },
41 |
42 | externals: [
43 | 'keytar',
44 | 'openpgp',
45 | 'font-awesome',
46 | 'source-map-support',
47 | ],
48 | }
49 |
--------------------------------------------------------------------------------
/webpack.config.node.js:
--------------------------------------------------------------------------------
1 | // for babel-plugin-webpack-loaders
2 | require('babel-register')
3 | const devConfigs = require('./webpack.config.development')
4 |
5 | module.exports = {
6 | output: {
7 | libraryTarget: 'commonjs2',
8 | },
9 | module: {
10 | loaders: devConfigs.module.loaders,
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack'
2 | import ExtractTextPlugin from 'extract-text-webpack-plugin'
3 | import baseConfig from './webpack.config.base'
4 |
5 | const config = {
6 | ...baseConfig,
7 |
8 | devtool: 'source-map',
9 |
10 | entry: [
11 | 'babel-polyfill',
12 | './app/index',
13 | ],
14 |
15 | output: {
16 | ...baseConfig.output,
17 |
18 | publicPath: '../dist/',
19 | },
20 |
21 | module: {
22 | ...baseConfig.module,
23 |
24 | loaders: [
25 | ...baseConfig.module.loaders,
26 |
27 | {
28 | test: /\.global\.css$/,
29 | loader: ExtractTextPlugin.extract(
30 | 'style-loader',
31 | 'css-loader'
32 | ),
33 | },
34 |
35 | {
36 | test: /^((?!\.global).)*\.css$/,
37 | loader: ExtractTextPlugin.extract(
38 | 'style-loader',
39 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
40 | ),
41 | },
42 | ],
43 | },
44 |
45 | plugins: [
46 | ...baseConfig.plugins,
47 | new webpack.optimize.OccurenceOrderPlugin(),
48 | new webpack.DefinePlugin({
49 | __DEV__: false,
50 | 'process.env': {
51 | NODE_ENV: JSON.stringify('production'),
52 | },
53 | }),
54 | new webpack.optimize.UglifyJsPlugin({
55 | compressor: {
56 | screw_ie8: true,
57 | warnings: false,
58 | },
59 | }),
60 | new ExtractTextPlugin('style.css', { allChunks: true }),
61 | ],
62 |
63 | externals: [
64 | 'keytar',
65 | 'openpgp',
66 | ],
67 |
68 | target: 'electron-renderer',
69 | }
70 |
71 | export default config
72 |
--------------------------------------------------------------------------------