diff --git a/email_app/.compilerc b/email_app/.compilerc index d5cc5d8..e5d1932 100644 --- a/email_app/.compilerc +++ b/email_app/.compilerc @@ -26,5 +26,8 @@ ] } } + }, + "text/less": { + "dumpLineNumbers": "comments" } } \ No newline at end of file diff --git a/email_app/.gitignore b/email_app/.gitignore index 845ddcb..af9c934 100644 --- a/email_app/.gitignore +++ b/email_app/.gitignore @@ -8,3 +8,4 @@ Thumbs.db /dist /main.js /main.js.map +/out \ No newline at end of file diff --git a/email_app/README.md b/email_app/README.md index d99e3fa..b634bb5 100644 --- a/email_app/README.md +++ b/email_app/README.md @@ -64,3 +64,9 @@ To package apps with options: ```bash $ npm run package -- --[option] ``` + +## Application Data Model + +The following diagram depicts how the emails are stored in the SAFE network, as well as how the email app stores email accounts information. + +![Email App Data Model](./design/EmailApp-DataModel.png) diff --git a/email_app/app/actions/actionTypes.js b/email_app/app/actions/actionTypes.js index eac28c9..1272290 100644 --- a/email_app/app/actions/actionTypes.js +++ b/email_app/app/actions/actionTypes.js @@ -1,21 +1,24 @@ const ACTION_TYPES = { + // Initializer AUTHORISE_APP: 'AUTHORISE_APP', GET_CONFIG: 'GET_CONFIG', REFRESH_EMAIL: 'REFRESH_EMAIL', SET_INITIALIZER_TASK: 'SET_INITIALIZER_TASK', + STORE_NEW_ACCOUNT: 'STORE_NEW_ACCOUNT', - // legacy + // Create Account CREATE_ACCOUNT: 'CREATE_ACCOUNT', CREATE_ACCOUNT_ERROR: 'SET_CREATE_ACCOUNT_ERROR', + // Mail Inbox PUSH_MAIL: 'PUSH_MAIL', - SET_MAIL_PROCESSING: 'SET_MAIL_PROCESSING', + MAIL_PROCESSING: 'MAIL_PROCESSING', CLEAR_MAIL_PROCESSING: 'CLEAR_MAIL_PROCESSING', SET_ACTIVE_MAIL: 'SET_ACTIVE_MAIL', CANCEL_COMPOSE: 'CANCEL_COMPOSE', - CLEAR_INBOX: 'CLEAR_INBOX', - PUSH_TO_INBOX: 'PUSH_TO_INBOX' + PUSH_TO_INBOX: 'PUSH_TO_INBOX', + PUSH_TO_ARCHIVE: 'PUSH_TO_ARCHIVE' }; export default ACTION_TYPES; diff --git a/email_app/app/actions/create_account_actions.js b/email_app/app/actions/create_account_actions.js index 8569023..cb920ea 100644 --- a/email_app/app/actions/create_account_actions.js +++ b/email_app/app/actions/create_account_actions.js @@ -1,28 +1,13 @@ -import { CONSTANTS } from '../constants'; import ACTION_TYPES from './actionTypes'; - -var accountResolver; -var accountRejecter; -const accountPromise = new Promise((resolve, reject) => { - accountResolver = resolve; - accountRejecter = reject; -}); +import { setupAccount } from '../safenet_comm'; export const createAccount = (emailId) => { return function (dispatch, getState) { - dispatch({ + let app = getState().initializer.app; + return dispatch({ type: ACTION_TYPES.CREATE_ACCOUNT, - payload: accountPromise + payload: setupAccount(app, emailId) }); - - // FIXME: store private key for encryption in app's container mapped to emailId - - let app = getState().initializer.app; - return app.mutableData.newRandomPublic(CONSTANTS.INBOX_TAG_TYPE) - .then((md) => md.quickSetup({})) - // FIXME: map this address to emailId in publicNames - .then((md) => accountResolver(md)) - .catch(accountRejecter); }; }; diff --git a/email_app/app/actions/initializer_actions.js b/email_app/app/actions/initializer_actions.js index a2da244..48dc2ef 100644 --- a/email_app/app/actions/initializer_actions.js +++ b/email_app/app/actions/initializer_actions.js @@ -1,65 +1,81 @@ -import { initializeApp, fromAuthURI } from 'safe-app'; - import ACTION_TYPES from './actionTypes'; - -var authResolver; -var authRejecter; -const authPromise = () => new Promise((resolve, reject) => { - authResolver = resolve; - authRejecter = reject; -}); +import { authApp, connect, readConfig, writeConfig, + readInboxEmails, readArchivedEmails } from '../safenet_comm'; export const setInitializerTask = (task) => ({ type: ACTION_TYPES.SET_INITIALIZER_TASK, task }); -export const receiveResponse = (uri) => { +export const onAuthFailure = (err) => { return { type: ACTION_TYPES.AUTHORISE_APP, - payload: fromAuthURI(uri) - .then((app) => authResolver ? authResolver(app) : app) - } + payload: Promise.reject(err) + }; }; -export const authoriseApplication = (appInfo, permissions, opts) => { - +export const receiveResponse = (uri) => { return function (dispatch) { - dispatch({ + return dispatch({ type: ACTION_TYPES.AUTHORISE_APP, - payload: authPromise() + payload: connect(uri) }); + }; +}; - return initializeApp(appInfo) - .then((app) => - process.env.SAFE_FAKE_AUTH - ? app.auth.loginForTest(permissions, opts) - .then(app => authResolver(app)) - : app.auth.genAuthUri(permissions, opts) - .then(resp => app.auth.openUri(resp.uri)) - ).catch(authRejecter); - +export const authoriseApplication = () => { + return function (dispatch) { + return dispatch({ + type: ACTION_TYPES.AUTHORISE_APP, + payload: new Promise((resolve, reject) => { + authApp() + .then(resolve) + .catch(reject); + }) + }) + .catch(_ => {}); }; }; export const refreshConfig = () => { - return function (dispatch, getState) { - dispatch({ + let app = getState().initializer.app; + return dispatch({ type: ACTION_TYPES.GET_CONFIG, - payload: authPromise() + payload: readConfig(app) + }); + }; +}; + +export const storeNewAccount = (account) => { + return function (dispatch, getState) { + let app = getState().initializer.app; + return dispatch({ + type: ACTION_TYPES.STORE_NEW_ACCOUNT, + payload: writeConfig(app, account) }); + }; +}; - let accounts = {}; +export const refreshEmail = (account) => { + return function (dispatch, getState) { let app = getState().initializer.app; - return app.auth.refreshContainerAccess() - .then(() => app.auth.getHomeContainer()) - .then((mdata) => mdata.getEntries() - .then((entries) => entries.forEach((name, valV) => { - accounts[name.toString()] = valV.buf.toString(); - }) - .then(() => authResolver(accounts)) - ) - ).catch(authRejecter); + return dispatch({ + type: ACTION_TYPES.REFRESH_EMAIL, + payload: readInboxEmails(app, account, + (inboxEntry) => { + dispatch({ + type: ACTION_TYPES.PUSH_TO_INBOX, + payload: inboxEntry + }); + }) + .then(() => readArchivedEmails(app, account, + (archiveEntry) => { + dispatch({ + type: ACTION_TYPES.PUSH_TO_ARCHIVE, + payload: archiveEntry + }); + })) + }); }; }; diff --git a/email_app/app/actions/mail_actions.js b/email_app/app/actions/mail_actions.js index e992c3e..3ae1873 100644 --- a/email_app/app/actions/mail_actions.js +++ b/email_app/app/actions/mail_actions.js @@ -1,22 +1,58 @@ import ACTION_TYPES from './actionTypes'; +import { storeEmail, removeInboxEmail, removeArchivedEmail, archiveEmail } from '../safenet_comm'; -export const setMailProcessing = () => ({ - type: ACTION_TYPES.SET_MAIL_PROCESSING -}); +export const sendEmail = (email, to) => { + return function (dispatch, getState) { + let app = getState().initializer.app; + return dispatch({ + type: ACTION_TYPES.MAIL_PROCESSING, + payload: storeEmail(app, email, to) + .then(() => dispatch(clearMailProcessing)) + .then(() => Promise.resolve()) + }); + }; +}; -export const clearMailProcessing= _ => ({ - type: ACTION_TYPES.CLEAR_MAIL_PROCESSING -}); +export const saveEmail = (account, key) => { + return function (dispatch, getState) { + let app = getState().initializer.app; + return dispatch({ + type: ACTION_TYPES.MAIL_PROCESSING, + payload: archiveEmail(app, account, key) + .then(() => dispatch(clearMailProcessing)) + .then(() => Promise.resolve()) + }); + }; +}; -export const setActiveMail = (data) => ({ - type: ACTION_TYPES.SET_ACTIVE_MAIL, - data -}); +export const deleteInboxEmail = (account, key) => { + return function (dispatch, getState) { + let app = getState().initializer.app; + return dispatch({ + type: ACTION_TYPES.MAIL_PROCESSING, + payload: removeInboxEmail(app, account, key) + .then(() => dispatch(clearMailProcessing)) + .then(() => Promise.resolve()) + }); + }; +}; + +export const deleteSavedEmail = (account, key) => { + return function (dispatch, getState) { + let app = getState().initializer.app; + return dispatch({ + type: ACTION_TYPES.MAIL_PROCESSING, + payload: removeArchivedEmail(app, account, key) + .then(() => dispatch(clearMailProcessing)) + .then(() => Promise.resolve()) + }); + }; +}; -export const clearInbox = () => ({ - type: ACTION_TYPES.SET_ACTIVE_MAIL +export const clearMailProcessing = _ => ({ + type: ACTION_TYPES.CLEAR_MAIL_PROCESSING }); -export const cancelCompose = () => ({ +export const cancelCompose = _ => ({ type: ACTION_TYPES.CANCEL_COMPOSE }); diff --git a/email_app/app/actions/nfs_actions.js b/email_app/app/actions/nfs_actions.js deleted file mode 100644 index 71e7500..0000000 --- a/email_app/app/actions/nfs_actions.js +++ /dev/null @@ -1,16 +0,0 @@ -import ACTION_TYPES from './actionTypes'; -import { CONSTANTS } from '../constants'; - -export const writeConfigFile = (coreId) => { - // FIXME: are these even needed? - return { - type: ACTION_TYPES.WRITE_CONFIG_FILE, - coreId - } -}; - -export const getConfigFile = () => { - return { - type: ACTION_TYPES.GET_CONFIG_FILE - }; -}; diff --git a/email_app/app/app.html b/email_app/app/app.html index 899999d..801f634 100755 --- a/email_app/app/app.html +++ b/email_app/app/app.html @@ -8,9 +8,10 @@ + -
+
- \ No newline at end of file + diff --git a/email_app/app/app.js b/email_app/app/app.js index 2659747..6ee9b8d 100644 --- a/email_app/app/app.js +++ b/email_app/app/app.js @@ -6,23 +6,18 @@ import { syncHistoryWithStore } from 'react-router-redux'; import { ipcRenderer as ipc } from 'electron'; import routes from './routes'; import configureStore from './store/configureStore'; -import { receiveResponse } from "./actions/initializer_actions"; +import { receiveResponse, onAuthFailure } from "./actions/initializer_actions"; const store = configureStore(); const history = syncHistoryWithStore(hashHistory, store); - -const listenForAuthReponse = (event, response) => { - // TODO parse response - if (response) { +ipc.on('auth-response', (event, response) => { + if (response && response.indexOf('safe-') == 0) { store.dispatch(receiveResponse(response)); // TODO do it concurrently (no to linked dispatch) } else { - // store.dispatch(onAuthFailure(new Error('Authorisation failed'))); + store.dispatch(onAuthFailure(new Error('Authorisation failed'))); } -}; - -ipc.on('auth-response', listenForAuthReponse); - +}); export default class App extends React.Component { render() { @@ -30,4 +25,4 @@ export default class App extends React.Component { ); } -} \ No newline at end of file +} diff --git a/email_app/app/components/compose_mail.js b/email_app/app/components/compose_mail.js index 20d5d9e..b712efd 100644 --- a/email_app/app/components/compose_mail.js +++ b/email_app/app/components/compose_mail.js @@ -1,78 +1,19 @@ -import React, { Component, PropTypes } from 'react'; -import * as base64 from 'urlsafe-base64'; -import { hashEmailId, showError, showSuccess } from '../utils/app_utils'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { showError, showSuccess } from '../utils/app_utils'; import { CONSTANTS } from '../constants'; export default class ComposeMail extends Component { - static contextTypes = { - router: PropTypes.object.isRequired - }; - constructor() { super(); - this.newMail = {}; this.tempMailContent = null; - this.newMailId = null; - this.appendableDataId = null; this.sendMail = this.sendMail.bind(this); - this.createMail = this.createMail.bind(this); this.handleTextLimit = this.handleTextLimit.bind(this); + this.handleCancel = this.handleCancel.bind(this); } - - - createMail(cipherHandleId) { - const { token, createImmutableDataWriterHandle, writeImmutableData, putImmutableData, closeImmutableDataWriter, clearMailProcessing } = this.props; - - const dropWriter = (writerHandle) => { - closeImmutableDataWriter(token, writerHandle) - .then(res => { - if (res.error) { - return console.error('Drop Immutable Data Writer Handle Error', res.error.message); - } - console.warn('Immutable Data Writer Handle Dropped'); - }); - }; - - const save = (writerHandleId) => { - putImmutableData(token, writerHandleId, cipherHandleId) - .then(res => { - if (res.error) { - clearMailProcessing(); - return showError('Create Immutable Data Writer Error', res.error.message); - } - dropWriter(writerHandleId); - return this.appendAppendableData(res.payload.data.handleId); - }); - }; - - const write = (writerHandleId) => { - writeImmutableData(token, writerHandleId, this.newMail) - .then(res => { - if (res.error) { - clearMailProcessing(); - return showError('Create Immutable Data Writer Error', res.error.message); - } - return save(writerHandleId); - }) - }; - - const createImmutWriter = () => { - createImmutableDataWriterHandle(token) - .then(res => { - if (res.error) { - clearMailProcessing(); - return showError('Create Immutable Data Writer Error', res.error.message); - } - return write(res.payload.data.handleId); - }); - }; - createImmutWriter(); - } - - sendMail(e) { - const { token, fromMail, setMailProcessing } = this.props; + const { app, fromMail, sendEmail } = this.props; e.preventDefault(); const mailTo = this.mailTo.value.trim(); @@ -84,14 +25,16 @@ export default class ComposeMail extends Component { if (mailContent.length > CONSTANTS.MAIL_CONTENT_LIMIT) { return showError('Mail Content is too Long', 'Mail Content is too long!'); } - this.newMail = { + + let newEmail = { subject: mailSub, from: fromMail, time: (new Date()).toUTCString(), body: mailContent }; - setMailProcessing(); - return this.getAppendableDataIdHandle(mailTo); + return sendEmail(newEmail, mailTo) + .then(() => this.context.router.push('/home')) + .catch((err) => showError('Error sending email', err)); } handleCancel() { @@ -129,7 +72,7 @@ export default class ComposeMail extends Component {
+ }} required="required" defaultValue=" " />
Only { CONSTANTS.MAIL_CONTENT_LIMIT } characters allowed. (This is just a restriction in this tutorial to not handle multiple chunks for content)
@@ -147,3 +90,7 @@ export default class ComposeMail extends Component { ); } } + +ComposeMail.contextTypes = { + router: PropTypes.object.isRequired +}; diff --git a/email_app/app/components/create_account.js b/email_app/app/components/create_account.js index 16dcbf8..0ae6e05 100644 --- a/email_app/app/components/create_account.js +++ b/email_app/app/components/create_account.js @@ -1,19 +1,20 @@ -import React, { Component, PropTypes } from 'react'; -import { hashEmailId } from '../utils/app_utils'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { MESSAGES, CONSTANTS } from '../constants'; export default class CreateAccount extends Component { - static propTypes = { - }; - - static contextTypes = { - router: PropTypes.object.isRequired - }; - constructor() { super(); this.errMrg = null; this.handleCreateAccount = this.handleCreateAccount.bind(this); + this.storeCreatedAccount = this.storeCreatedAccount.bind(this); + } + + storeCreatedAccount() { + const { newAccount, storeNewAccount, createAccountError } = this.props; + return storeNewAccount(newAccount) + .then((_) => this.context.router.push('/home')) + .catch((e) => createAccountError(new Error(e))); } handleCreateAccount(e) { @@ -23,12 +24,20 @@ export default class CreateAccount extends Component { if (!emailId.trim()) { return; } + if (emailId.length > CONSTANTS.EMAIL_ID_MAX_LENGTH) { return createAccountError(new Error(MESSAGES.EMAIL_ID_TOO_LONG)); } + return createAccount(emailId) - .then(() => this.context.router.push('/home')); - } + .then(this.storeCreatedAccount) + .catch((err) => { + if (err.name === 'ERR_DATA_EXISTS') { + return createAccountError(new Error(MESSAGES.EMAIL_ALREADY_TAKEN)); + } + return createAccountError(err); + }); + }; render() { const { processing, error } = this.props; @@ -55,3 +64,7 @@ export default class CreateAccount extends Component { ); } } + +CreateAccount.contextTypes = { + router: PropTypes.object.isRequired +}; diff --git a/email_app/app/components/home.js b/email_app/app/components/home.js index 3bdcfa1..25da6ad 100755 --- a/email_app/app/components/home.js +++ b/email_app/app/components/home.js @@ -1,23 +1,13 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { Link, IndexLink } from 'react-router'; import className from 'classnames'; import pkg from '../../package.json'; export default class Home extends Component { - static contextTypes = { - router: PropTypes.object.isRequired - }; - - static propTypes = { - - }; - render() { const { router } = this.context; - const { coreData } = this.props; - const inboxLength = coreData.inbox.length; - const savedLength = coreData.saved.length; - const outboxLength = coreData.outbox.length; + const { coreData, inboxSize, savedSize } = this.props; return (
@@ -40,7 +30,7 @@ export default class Home extends Component { email Inbox - {inboxLength === 0 ? '' : `${inboxLength}`} + {inboxSize === 0 ? '' : `${inboxSize}`}
@@ -49,7 +39,7 @@ export default class Home extends Component { drafts Saved - {savedLength === 0 ? '' : `${savedLength}`} + {savedSize === 0 ? '' : `${savedSize}`}
@@ -62,3 +52,7 @@ export default class Home extends Component { ); } } + +Home.contextTypes = { + router: PropTypes.object.isRequired +}; diff --git a/email_app/app/components/initializer.js b/email_app/app/components/initializer.js index f7061ea..7790387 100644 --- a/email_app/app/components/initializer.js +++ b/email_app/app/components/initializer.js @@ -1,55 +1,51 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { remote } from 'electron'; -import { CONSTANTS, AUTH_PAYLOAD, MESSAGES } from '../constants'; +import { showError } from '../utils/app_utils'; +import { MESSAGES, APP_STATUS } from '../constants'; -const showDialog = (title, message) => { - remote.dialog.showMessageBox({ - type: 'error', - buttons: ['Ok'], - title, - message - }, _ => { remote.getCurrentWindow().close(); }); -}; +const showAuthError = _ => showError('Authorisation failed', + MESSAGES.AUTHORISATION_ERROR, + _ => { remote.getCurrentWindow().close(); }); export default class Initializer extends Component { - static contextTypes = { - router: PropTypes.object.isRequired - }; - constructor() { super(); - this.checkConfiguration = this.checkConfiguration.bind(this); + this.refreshConfig = this.refreshConfig.bind(this); } componentDidMount() { - this.props.authoriseApplication(AUTH_PAYLOAD, {"_publicNames" : ["Insert"]}) - .then((_) => this.checkConfiguration()) - .catch((err) => { - console.error(err) - return showDialog('Authorisation Error', MESSAGES.AUTHORISATION_ERROR); - }); - } + const { setInitializerTask, authoriseApplication } = this.props; + setInitializerTask(MESSAGES.INITIALIZE.AUTHORISE_APP); - checkConfiguration() { - const { app, refreshConfig } = this.props; - if (!app) { - throw new Error('Application client not found.'); - } + return authoriseApplication(); + } + refreshConfig() { + const { setInitializerTask, refreshConfig } = this.props; + setInitializerTask(MESSAGES.INITIALIZE.CHECK_CONFIGURATION); return refreshConfig() .then((_) => { if (Object.keys(this.props.accounts).length > 0) { return this.context.router.push('/home'); - } else { - return this.context.router.push('/create_account'); } + showAuthError(); }) - .catch((err) => { - console.error(err) - return showDialog('Error fetching configuration', MESSAGES.CHECK_CONFIGURATION_ERROR); + .catch((_) => { + console.log("No email account found"); + return this.context.router.push('/create_account'); }); } + componentDidUpdate(prevProps, prevState) { + const { app_status, app } = this.props; + if (prevProps.app_status === APP_STATUS.AUTHORISING + && app_status === APP_STATUS.AUTHORISATION_FAILED) { + showAuthError(); + } else if (app && app_status === APP_STATUS.AUTHORISED) { + return this.refreshConfig(); + } + } render() { const { tasks } = this.props; @@ -70,3 +66,7 @@ export default class Initializer extends Component { ); } } + +Initializer.contextTypes = { + router: PropTypes.object.isRequired +}; diff --git a/email_app/app/components/mail_inbox.js b/email_app/app/components/mail_inbox.js index 34a932f..f9e1f59 100644 --- a/email_app/app/components/mail_inbox.js +++ b/email_app/app/components/mail_inbox.js @@ -1,44 +1,32 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component } from 'react'; import MailList from './mail_list'; -import * as base64 from 'urlsafe-base64'; -import { showError, hashEmailId } from '../utils/app_utils'; +import { showError } from '../utils/app_utils'; import { CONSTANTS } from '../constants'; export default class MailInbox extends Component { constructor() { super(); - this.appendableDataHandle = 0; - this.dataLength = 0; - this.currentIndex = 0; - this.refresh = this.refresh.bind(this); this.fetchMails = this.fetchMails.bind(this); - this.fetchMail = this.fetchMail.bind(this); - this.refresh = this.refresh.bind(this); } componentDidMount() { this.fetchMails(); } - fetchMail(id) { - - } - - fetchMails() { - const { clearInbox, setMailProcessing, accounts } = this.props; - console.log("ACC:", accounts); -// clearInbox(); -// setMailProcessing(); -// return this.getAppendableDataIdHandle(); - } - - refresh(e) { + fetchMails(e) { if (e) { e.preventDefault(); } - this.currentIndex = 0; - this.dataLength = 0; - this.fetchMails(); + + const { refreshEmail, accounts } = this.props; + // TODO: Eventually the app can allow to choose which email account, + // it now supports only one. + let chosenAccount = accounts; + refreshEmail(chosenAccount) + .catch((error) => { + console.error('Failed fetching emails: ', error); + showError('Failed fetching emails: ', error); + }); } render() { @@ -49,7 +37,7 @@ export default class MailInbox extends Component { Inbox Space Used: {this.props.inboxSize}KB of {CONSTANTS.TOTAL_INBOX_SIZE}KB
-
diff --git a/email_app/app/components/mail_list.js b/email_app/app/components/mail_list.js index 5dfb5ae..9b78ae1 100644 --- a/email_app/app/components/mail_list.js +++ b/email_app/app/components/mail_list.js @@ -1,23 +1,18 @@ -import React, { Component, PropTypes } from 'react'; -import { remote } from 'electron'; -import * as base64 from 'urlsafe-base64'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import dateformat from 'dateformat'; -import { showError, showSuccess, hashEmailId } from '../utils/app_utils'; +import { showError } from '../utils/app_utils'; import { CONSTANTS } from '../constants'; export default class MailList extends Component { - static contextTypes = { - router: PropTypes.object.isRequired - }; - constructor() { super(); - this.appendableHandlerId = null; this.listColors = {}; - this.activeIndex = null; this.activeType = null; this.goBack = this.goBack.bind(this); - this.handleDelete = this.handleDelete.bind(this); + this.refreshEmail = this.refreshEmail.bind(this); + this.handleDeleteFromInbox = this.handleDeleteFromInbox.bind(this); + this.handleDeleteSaved = this.handleDeleteSaved.bind(this); this.handleSave = this.handleSave.bind(this); } @@ -26,7 +21,7 @@ export default class MailList extends Component { switch (this.activeType) { case CONSTANTS.HOME_TABS.INBOX: { - return this.props.inbox.refresh(); + return this.props.refreshEmail(); } case CONSTANTS.HOME_TABS.SAVED: { router.push('/home'); @@ -35,35 +30,72 @@ export default class MailList extends Component { } } - handleDelete(e) { + refreshEmail(account) { + this.props.refreshEmail(account) + .catch((error) => { + console.error('Fetching emails failed: ', error); + showError('Fetching emails failed: ', error); + }); + } + + handleDeleteFromInbox(e) { e.preventDefault(); + const { accounts, deleteInboxEmail, refreshEmail } = this.props; + deleteInboxEmail(accounts, e.target.dataset.index) + .catch((error) => { + console.error('Failed trying to delete email from inbox: ', error); + showError('Failed trying to delete email from inbox: ', error); + }) + .then(() => this.refreshEmail(accounts)) + } + + handleDeleteSaved(e) { + e.preventDefault(); + const { accounts, deleteSavedEmail, refreshEmail } = this.props; + deleteSavedEmail(accounts, e.target.dataset.index) + .catch((error) => { + console.error('Failed trying to delete saved email: ', error); + showError('Failed trying to delete saved email: ', error); + }) + .then(() => this.refreshEmail(accounts)) } handleSave(e) { e.preventDefault(); + const { accounts, saveEmail, refreshEmail } = this.props; + // TODO: Eventually the app can allow to choose which email account, + // it now supports only one. + let chosenAccount = accounts; + saveEmail(chosenAccount, e.target.dataset.index) + .catch((error) => { + console.error('Failed trying to save the email: ', error); + showError('Failed trying to save the email: ', error); + }) + .then(() => this.refreshEmail(chosenAccount)) } render() { const self = this; - const { processing, coreData, error, inbox, outbox, saved } = this.props; + const { processing, coreData, error, inboxSize, inbox, savedSize, saved } = this.props; let container = null; if (processing) { container =
  • Loading...
  • } else if (Object.keys(error).length > 0) { - container =
  • Error in fetching mails!
  • + container =
  • Error in fetching emails!
  • } else { if (inbox) { this.activeType = CONSTANTS.HOME_TABS.INBOX; container = (
    { - coreData.inbox.length === 0 ?
  • Inbox empty
  • : coreData.inbox.map((mail, i) => { + inboxSize === 0 ?
  • Inbox empty
  • : Object.keys(coreData.inbox).map((key) => { + let mail = coreData.inbox[key]; if (!self.listColors.hasOwnProperty(mail.from)) { self.listColors[mail.from] = `bg-color-${Object.keys(self.listColors).length % 10}` } return ( -
  • +
  • {mail.from[0]}
    @@ -75,10 +107,10 @@ export default class MailList extends Component {
  • - +
    - +
    @@ -93,7 +125,8 @@ export default class MailList extends Component { container = (
    { - coreData.saved.length === 0 ?
  • Saved empty
  • : coreData.saved.map((mail, i) => { + savedSize === 0 ?
  • Saved empty
  • : Object.keys(coreData.saved).map((key) => { + let mail = coreData.saved[key]; if (!mail) { return; } @@ -101,7 +134,7 @@ export default class MailList extends Component { self.listColors[mail.from] = `bg-color-${Object.keys(self.listColors).length % 10}` } return ( -
  • +
  • {mail.from[0]}
    @@ -113,7 +146,7 @@ export default class MailList extends Component {
  • - +
    @@ -131,3 +164,7 @@ export default class MailList extends Component { ); } } + +MailList.contextTypes = { + router: PropTypes.object.isRequired +}; diff --git a/email_app/app/components/mail_saved.js b/email_app/app/components/mail_saved.js index 9994065..edf2b31 100644 --- a/email_app/app/components/mail_saved.js +++ b/email_app/app/components/mail_saved.js @@ -1,4 +1,4 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component } from 'react'; import MailList from './mail_list'; export default class MailSaved extends Component { diff --git a/email_app/app/constants.js b/email_app/app/constants.js index 1036cf9..cef0ddc 100644 --- a/email_app/app/constants.js +++ b/email_app/app/constants.js @@ -1,24 +1,32 @@ -import pkg from '../package.json'; - export const CONSTANTS = { LOCAL_AUTH_DATA_KEY: 'local_auth_data_key', - INBOX_TAG_TYPE: 15003, - ENCRYPTION: { - PLAIN: 'PLAIN', - SYMMETRIC: 'SYMMETRIC', - ASYMMETRIC: 'ASYMMETRIC' - }, + TAG_TYPE_DNS: 15001, + TAG_TYPE_INBOX: 15003, + TAG_TYPE_EMAIL_ARCHIVE: 15004, + SERVICE_NAME_POSTFIX: "@email", + MD_KEY_EMAIL_INBOX: "email_inbox", + MD_KEY_EMAIL_ARCHIVE: "email_archive", + MD_KEY_EMAIL_ID: "email_id", + MD_KEY_EMAIL_ENC_SECRET_KEY: "__email_enc_sk", + MD_KEY_EMAIL_ENC_PUBLIC_KEY: "__email_enc_pk", TOTAL_INBOX_SIZE: 100, EMAIL_ID_MAX_LENGTH: 100, HOME_TABS: { INBOX: 'INBOX', - OUTBOX: 'OUTBOX', SAVED: 'SAVED' }, MAIL_CONTENT_LIMIT: 150, DATE_FORMAT: 'h:MM-mmm dd' }; +export const APP_STATUS = { + AUTHORISING: 'AUTHORISING', + AUTHORISATION_FAILED: 'AUTHORISATION_FAILED', + AUTHORISED: 'AUTHORISED', + READING_CONFIG: 'READING_CONFIG', + READY: 'READY' +} + export const MESSAGES = { INITIALIZE: { AUTHORISE_APP: 'Authorising Application', @@ -29,12 +37,7 @@ export const MESSAGES = { }, EMAIL_ALREADY_TAKEN: 'Email ID already taken. Please try again', EMAIL_ID_TOO_LONG: 'Email ID is too long', + EMAIL_ID_NOT_FOUND: 'Email ID not found on the network', AUTHORISATION_ERROR: 'Failed to authorise', CHECK_CONFIGURATION_ERROR: 'Failed to retrieve configuration' }; - -export const AUTH_PAYLOAD = { - id: pkg.identifier, - name: pkg.productName, - vendor: pkg.vendor -}; diff --git a/email_app/app/containers/App.js b/email_app/app/containers/App.js index 93a4ebc..b9f5cf5 100755 --- a/email_app/app/containers/App.js +++ b/email_app/app/containers/App.js @@ -1,10 +1,7 @@ -import React, { Component, PropTypes } from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; export default class App extends Component { - static propTypes = { - children: PropTypes.element.isRequired - }; - render() { return (
    @@ -13,3 +10,7 @@ export default class App extends Component { ); } } + +App.propTypes = { + children: PropTypes.element.isRequired +}; diff --git a/email_app/app/containers/compose_mail_container.js b/email_app/app/containers/compose_mail_container.js index 0c28f2f..11ae136 100644 --- a/email_app/app/containers/compose_mail_container.js +++ b/email_app/app/containers/compose_mail_container.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; import ComposeMail from '../components/compose_mail'; -import { cancelCompose, setMailProcessing, clearMailProcessing } from '../actions/mail_actions'; +import { cancelCompose, sendEmail, clearMailProcessing } from '../actions/mail_actions'; const mapStateToProps = state => { return { - token: state.initializer.token, + app: state.initializer.app, fromMail: state.initializer.coreData.id, error: state.mail.error, processing: state.mail.processing @@ -13,9 +13,9 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - setMailProcessing: _ => (dispatch(setMailProcessing())), + sendEmail: (email, to) => (dispatch(sendEmail(email, to))), clearMailProcessing: _ => (dispatch(clearMailProcessing())), - cancelCompose: _ => dispatch(cancelCompose()), + cancelCompose: _ => dispatch(cancelCompose()) }; }; diff --git a/email_app/app/containers/create_account_container.js b/email_app/app/containers/create_account_container.js index 7b0c196..3012740 100644 --- a/email_app/app/containers/create_account_container.js +++ b/email_app/app/containers/create_account_container.js @@ -1,10 +1,14 @@ import { connect } from 'react-redux'; import CreateAccount from '../components/create_account'; import { createAccount, createAccountError } from '../actions/create_account_actions'; +import { storeNewAccount } from '../actions/initializer_actions'; const mapStateToProps = state => { return { - error: state.createAccount.error + error: state.createAccount.error, + processing: state.createAccount.processing, + newAccount: state.createAccount.newAccount, + coreData: state.initializer.coreData }; }; @@ -12,6 +16,7 @@ const mapDispatchToProps = dispatch => { return { createAccountError: error => (dispatch(createAccountError(error))), createAccount: emailId => (dispatch(createAccount(emailId))), + storeNewAccount: account => (dispatch(storeNewAccount(account))) }; }; diff --git a/email_app/app/containers/home_container.js b/email_app/app/containers/home_container.js index b03aa28..190c4a0 100644 --- a/email_app/app/containers/home_container.js +++ b/email_app/app/containers/home_container.js @@ -3,7 +3,9 @@ import Home from '../components/home'; const mapStateToProps = state => { return { - coreData: state.initializer.coreData + coreData: state.initializer.coreData, + inboxSize: state.initializer.inboxSize, + savedSize: state.initializer.savedSize }; }; diff --git a/email_app/app/containers/initializer_container.js b/email_app/app/containers/initializer_container.js index 9597367..7e7571c 100644 --- a/email_app/app/containers/initializer_container.js +++ b/email_app/app/containers/initializer_container.js @@ -1,13 +1,14 @@ import { connect } from 'react-redux'; import Initializer from '../components/initializer'; -import { setInitializerTask, authoriseApplication, refreshConfig } from '../actions/initializer_actions'; +import { setInitializerTask, authoriseApplication, + refreshConfig, refreshEmail } from '../actions/initializer_actions'; const mapStateToProps = state => { return { + app_status: state.initializer.app_status, app: state.initializer.app, accounts: state.initializer.accounts, tasks: state.initializer.tasks, - config: state.initializer.config, coreData: state.initializer.coreData }; }; @@ -15,9 +16,9 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { setInitializerTask: task => (dispatch(setInitializerTask(task))), - authoriseApplication: (appInfo, permissions, opts) => - (dispatch(authoriseApplication(appInfo, permissions, opts))), - refreshConfig: () => (dispatch(refreshConfig())) + authoriseApplication: () => (dispatch(authoriseApplication())), + refreshConfig: () => (dispatch(refreshConfig())), + refreshEmail: (account) => (dispatch(refreshEmail(account))) }; }; diff --git a/email_app/app/containers/mail_inbox_container.js b/email_app/app/containers/mail_inbox_container.js index c2a38d5..0eed22c 100644 --- a/email_app/app/containers/mail_inbox_container.js +++ b/email_app/app/containers/mail_inbox_container.js @@ -1,6 +1,7 @@ import { connect } from 'react-redux'; import MailInbox from '../components/mail_inbox'; -import { setMailProcessing, clearMailProcessing, setActiveMail, clearInbox } from '../actions/mail_actions'; +import { refreshInbox, clearMailProcessing, deleteInboxEmail, saveEmail } from '../actions/mail_actions'; +import { refreshEmail } from '../actions/initializer_actions'; const mapStateToProps = state => { return { @@ -8,6 +9,7 @@ const mapStateToProps = state => { error: state.mail.error, coreData: state.initializer.coreData, inboxSize: state.initializer.inboxSize, + savedSize: state.initializer.savedSize, app: state.initializer.app, accounts: state.initializer.accounts }; @@ -15,10 +17,10 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - setMailProcessing: () => (dispatch(setMailProcessing())), + refreshEmail: (account) => (dispatch(refreshEmail(account))), + deleteInboxEmail: (account, key) => (dispatch(deleteInboxEmail(account, key))), clearMailProcessing: () => (dispatch(clearMailProcessing())), - setActiveMail: data => (dispatch(setActiveMail(data))), - clearInbox: _ => (dispatch(clearInbox())), + saveEmail: (account, key) => (dispatch(saveEmail(account, key))) }; }; export default connect(mapStateToProps, mapDispatchToProps)(MailInbox); diff --git a/email_app/app/containers/mail_saved_container.js b/email_app/app/containers/mail_saved_container.js index facba6e..a45282a 100644 --- a/email_app/app/containers/mail_saved_container.js +++ b/email_app/app/containers/mail_saved_container.js @@ -1,6 +1,7 @@ import { connect } from 'react-redux'; import MailSaved from '../components/mail_saved'; -import { setMailProcessing } from '../actions/mail_actions'; +import { deleteSavedEmail } from '../actions/mail_actions'; +import { refreshEmail } from '../actions/initializer_actions'; const mapStateToProps = state => { return { @@ -14,7 +15,8 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - setMailProcessing: () => (dispatch(setMailProcessing())), + refreshEmail: (account) => (dispatch(refreshEmail(account))), + deleteSavedEmail: (account, key) => (dispatch(deleteSavedEmail(account, key))) }; }; diff --git a/email_app/app/index.js b/email_app/app/index.js index 75cef93..7378206 100755 --- a/email_app/app/index.js +++ b/email_app/app/index.js @@ -1,23 +1,14 @@ import { app, BrowserWindow } from 'electron'; -import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer'; -import { enableLiveReload } from 'electron-compile'; -require("babel-polyfill"); - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow; - -const sendResponse = (success) => { - mainWindow.webContents.send('auth-response', success ? success : ''); +const sendResponse = (res) => { + mainWindow.webContents.send('auth-response', res ? res : ''); }; -const isDevMode = process.execPath.match(/[\\/]electron/); - -// if (isDevMode) enableLiveReload({strategy: 'react-hmr'}); - -const createWindow = async () => { +const createWindow = () => { // Create the browser window. mainWindow = new BrowserWindow({ width: 800, @@ -28,10 +19,7 @@ const createWindow = async () => { mainWindow.loadURL(`file://${__dirname}/app.html`); // Open the DevTools. - if (isDevMode) { - await installExtension(REACT_DEVELOPER_TOOLS); - mainWindow.webContents.openDevTools(); - } + mainWindow.webContents.openDevTools(); // Emitted when the window is closed. mainWindow.on('closed', () => { @@ -40,15 +28,6 @@ const createWindow = async () => { // when you should delete the corresponding element. mainWindow = null; }); -}; - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. - - -app.on('ready', async () => { - await createWindow(); const shouldQuit = app.makeSingleInstance(function(commandLine) { if (commandLine.length >= 2 && commandLine[1]) { @@ -65,8 +44,13 @@ app.on('ready', async () => { if (shouldQuit) { app.quit(); } -}); +}; + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow); // Quit when all windows are closed. app.on('window-all-closed', () => { @@ -85,9 +69,6 @@ app.on('activate', () => { } }); -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and import them here. - app.on('open-url', function (e, url) { sendResponse(url); -}); \ No newline at end of file +}); diff --git a/email_app/app/less/main.less b/email_app/app/less/main.less index 781eb34..6e0a5b7 100644 --- a/email_app/app/less/main.less +++ b/email_app/app/less/main.less @@ -1,11 +1,11 @@ -@import "material_icons"; -@import "variables"; -@import "text_components"; -@import "form"; -@import "buttons"; -@import "base"; -@import "authenticate"; -@import "home"; -@import "list"; -@import "compose_mail"; -@import "view_mail"; +@import "material_icons.less"; +@import "variables.less"; +@import "text_components.less"; +@import "form.less"; +@import "buttons.less"; +@import "base.less"; +@import "authenticate.less"; +@import "home.less"; +@import "list.less"; +@import "compose_mail.less"; +@import "view_mail.less"; diff --git a/email_app/app/reducers/create_account.js b/email_app/app/reducers/create_account.js index 2aa7636..80a2df6 100644 --- a/email_app/app/reducers/create_account.js +++ b/email_app/app/reducers/create_account.js @@ -2,8 +2,8 @@ import ACTION_TYPES from '../actions/actionTypes'; const initialState = { processing: false, - accounts: [], - error: {} + error: {}, + newAccount: null }; const createAccount = (state = initialState, action) => { @@ -12,10 +12,7 @@ const createAccount = (state = initialState, action) => { return { ...state, processing: true }; break; case `${ACTION_TYPES.CREATE_ACCOUNT}_SUCCESS`: { - let updatedCoreData = { ...state.coreData, id: action.payload }; - let updatedAccounts = state.accounts.push(action.payload); - return { ...state, coreData: updatedCoreData, - accounts: updatedAccounts, processing: false }; + return { ...state, processing: false, newAccount: action.payload }; break; } case ACTION_TYPES.CREATE_ACCOUNT_ERROR: diff --git a/email_app/app/reducers/initialiser.js b/email_app/app/reducers/initialiser.js index d3e633c..47ffc13 100644 --- a/email_app/app/reducers/initialiser.js +++ b/email_app/app/reducers/initialiser.js @@ -1,18 +1,18 @@ import ACTION_TYPES from '../actions/actionTypes'; -import { MESSAGES } from '../constants'; +import { MESSAGES, APP_STATUS } from '../constants'; const initialState = { - app: '', + app_status: null, + app: null, tasks: [], accounts: [], - config: null, coreData: { id: '', inbox: [], - saved: [], - outbox: [] + saved: [] }, - inboxSize: 0 + inboxSize: 0, + savedSize: 0 }; const initializer = (state = initialState, action) => { @@ -23,49 +23,54 @@ const initializer = (state = initialState, action) => { return { ...state, tasks }; break; } - case `${ACTION_TYPES.AUTHORISE_APP}_LOADING`: { - const tasks = state.tasks.slice(); - tasks.push(MESSAGES.INITIALIZE.AUTHORISE_APP); - return { ...state, tasks }; + case `${ACTION_TYPES.AUTHORISE_APP}_LOADING`: + return { ...state, app: null, app_status: APP_STATUS.AUTHORISING }; break; - } case `${ACTION_TYPES.AUTHORISE_APP}_SUCCESS`: - return { ...state, app: action.payload }; + return { ...state, app: action.payload, app_status: APP_STATUS.AUTHORISED }; break; - case `${ACTION_TYPES.GET_CONFIG}_LOADING`: { - const tasks = state.tasks.slice(); - tasks.push(MESSAGES.INITIALIZE.CHECK_CONFIGURATION); - return { ...state, tasks }; + case `${ACTION_TYPES.AUTHORISE_APP}_ERROR`: + return { ...state, app_status: APP_STATUS.AUTHORISATION_FAILED }; + break; + case `${ACTION_TYPES.GET_CONFIG}_LOADING`: + return { ...state, app_status: APP_STATUS.READING_CONFIG }; break; - } case `${ACTION_TYPES.GET_CONFIG}_SUCCESS`: - return { ...state, accounts: action.payload }; + return { ...state, + accounts: action.payload, + coreData: { ...state.coreData, id: action.payload.id }, + app_status: APP_STATUS.READY + }; + break; + case `${ACTION_TYPES.STORE_NEW_ACCOUNT}_SUCCESS`: + return { ...state, + accounts: action.payload, + coreData: { ...state.coreData, id: action.payload.id } + }; break; -/* case `${ACTION_TYPES.REFRESH_EMAIL}_SUCCESS`: - return { ...state, accounts: action.payload.data }; + case `${ACTION_TYPES.REFRESH_EMAIL}_LOADING`: + return { ...state, + coreData: { ...state.coreData, inbox: [], saved: [] }, + inboxSize: 0, + savedSize: 0 + }; break; case ACTION_TYPES.PUSH_TO_INBOX: { - const inbox = state.coreData.inbox.slice(); - inbox.push(action.data); - - return { - ...state, - coreData: { - ...state.coreData, - inbox - } + let inbox = Object.assign({}, state.coreData.inbox, action.payload); + return { ...state, + coreData: { ...state.coreData, inbox }, + inboxSize: Object.keys(inbox).length }; break; } - case ACTION_TYPES.CLEAR_INBOX: { - return { - ...state, - coreData: { - ...state.coreData, - inbox: [] - } + case ACTION_TYPES.PUSH_TO_ARCHIVE: { + let saved = Object.assign({}, state.coreData.saved, action.payload); + return { ...state, + coreData: { ...state.coreData, saved }, + savedSize: Object.keys(saved).length }; - }*/ + break; + } default: return state; break; diff --git a/email_app/app/safenet_comm.js b/email_app/app/safenet_comm.js new file mode 100644 index 0000000..7554c7d --- /dev/null +++ b/email_app/app/safenet_comm.js @@ -0,0 +1,256 @@ +import { CONSTANTS, MESSAGES } from './constants'; +import { initializeApp, fromAuthURI } from 'safe-app'; +import { getAuthData, saveAuthData, clearAuthData, hashPublicId, genRandomEntryKey, + genKeyPair, encrypt, decrypt, genServiceInfo } from './utils/app_utils'; +import pkg from '../package.json'; + +const APP_INFO = { + info: { + id: pkg.identifier, + scope: null, + name: pkg.productName, + vendor: pkg.vendor + }, + opts: { + own_container: true + }, + permissions: { + _publicNames: ['Read', 'Insert', 'Update'] + } +}; + +const requestAuth = () => { + return initializeApp(APP_INFO.info) + .then((app) => app.auth.genAuthUri(APP_INFO.permissions, APP_INFO.opts) + .then((resp) => app.auth.openUri(resp.uri)) + ); +} + +export const authApp = () => { + if (process.env.SAFE_FAKE_AUTH) { + return initializeApp(APP_INFO.info) + .then((app) => app.auth.loginForTest(APP_INFO.permissions)); + } + + let uri = getAuthData(); + if (uri) { + return fromAuthURI(APP_INFO.info, uri) + .then((registered_app) => registered_app.auth.refreshContainerAccess() + .then(() => registered_app) + .catch((err) => { + console.warn("Auth URI stored is not valid anymore, app needs to be re-authorised."); + clearAuthData(); + return requestAuth(); + }) + ); + } + + return requestAuth(); +} + +export const connect = (uri) => { + return fromAuthURI(APP_INFO.info, uri) + .then((app) => { + saveAuthData(uri); + return app; + }); +} + +export const readConfig = (app) => { + let account = {}; + return app.auth.refreshContainerAccess() + .then(() => app.auth.getHomeContainer()) + .then((md) => md.encryptKey(CONSTANTS.MD_KEY_EMAIL_INBOX).then((key) => md.get(key)) + .then((value) => app.mutableData.fromSerial(value.buf)) + .then((inbox_md) => account.inbox_md = inbox_md) + .then(() => md.encryptKey(CONSTANTS.MD_KEY_EMAIL_ARCHIVE).then((key) => md.get(key))) + .then((value) => app.mutableData.fromSerial(value.buf)) + .then((archive_md) => account.archive_md = archive_md) + .then(() => md.encryptKey(CONSTANTS.MD_KEY_EMAIL_ID).then((key) => md.get(key))) + .then((value) => account.id = value.buf.toString()) + .then(() => md.encryptKey(CONSTANTS.MD_KEY_EMAIL_ENC_SECRET_KEY).then((key) => md.get(key))) + .then((value) => account.enc_sk = value.buf.toString()) + .then(() => md.encryptKey(CONSTANTS.MD_KEY_EMAIL_ENC_PUBLIC_KEY).then((key) => md.get(key))) + .then((value) => account.enc_pk = value.buf.toString()) + ) + .then(() => account); +} + +export const writeConfig = (app, account) => { + let serialised_inbox; + let serialised_archive; + return account.inbox_md.serialise() + .then((serial) => serialised_inbox = serial) + .then(() => account.archive_md.serialise()) + .then((serial) => serialised_archive = serial) + .then(() => app.auth.refreshContainerAccess()) + .then(() => app.auth.getHomeContainer()) + .then((md) => app.mutableData.newMutation() + .then((mut) => mut.insert(CONSTANTS.MD_KEY_EMAIL_INBOX, serialised_inbox) + .then(() => mut.insert(CONSTANTS.MD_KEY_EMAIL_ARCHIVE, serialised_archive)) + .then(() => mut.insert(CONSTANTS.MD_KEY_EMAIL_ID, account.id)) + .then(() => mut.insert(CONSTANTS.MD_KEY_EMAIL_ENC_SECRET_KEY, account.enc_sk)) + .then(() => mut.insert(CONSTANTS.MD_KEY_EMAIL_ENC_PUBLIC_KEY, account.enc_pk)) + .then(() => md.applyEntriesMutation(mut)) + )) + .then(() => account); +} + +export const readInboxEmails = (app, account, cb) => { + return account.inbox_md.getEntries() + .then((entries) => entries.forEach((key, value) => { + if (key.toString() !== CONSTANTS.MD_KEY_EMAIL_ENC_PUBLIC_KEY + && value.buf.toString().length > 0) { //FIXME: this condition is a work around for a limitation in safe_core + let entry_value = decrypt(value.buf.toString(), account.enc_sk, account.enc_pk); + return app.immutableData.fetch(Buffer.from(entry_value, 'hex')) + .then((immData) => immData.read()) + .then((content) => { + let decryptedEmail; + decryptedEmail = JSON.parse(decrypt(content.toString(), account.enc_sk, account.enc_pk)); + cb({ [key]: decryptedEmail }); + }) + } + }) + ); +} + +export const readArchivedEmails = (app, account, cb) => { + return account.archive_md.getEntries() + .then((entries) => entries.forEach((key, value) => { + if (value.buf.toString().length > 0) { //FIXME: this condition is a work around for a limitation in safe_core + return app.immutableData.fetch(value.buf) + .then((immData) => immData.read()) + .then((content) => { + let decryptedEmail; + decryptedEmail = JSON.parse(decrypt(content.toString(), account.enc_sk, account.enc_pk)); + cb({ [key]: decryptedEmail }); + }) + } + }) + ); +} + +const createInbox = (app, enc_pk) => { + let base_inbox = { + [CONSTANTS.MD_KEY_EMAIL_ENC_PUBLIC_KEY]: enc_pk + }; + let inbox_md; + let permSet; + + return app.mutableData.newRandomPublic(CONSTANTS.TAG_TYPE_INBOX) + .then((md) => md.quickSetup(base_inbox)) + .then((md) => inbox_md = md) + .then(() => app.mutableData.newPermissionSet()) + .then((pmSet) => permSet = pmSet) + .then(() => permSet.setAllow('Insert')) + .then(() => inbox_md.setUserPermissions(null, permSet, 1)) + .then(() => inbox_md); +} + +const createArchive = (app) => { + return app.mutableData.newRandomPrivate(CONSTANTS.TAG_TYPE_EMAIL_ARCHIVE) + .then((md) => md.quickSetup()); +} + +const addEmailService = (app, serviceInfo, inbox_serialised) => { + return app.mutableData.newPublic(serviceInfo.serviceAddr, CONSTANTS.TAG_TYPE_DNS) + .then((md) => md.quickSetup({ [serviceInfo.serviceName]: inbox_serialised })) +} + +const createPublicIdAndEmailService = (app, pub_names_md, serviceInfo, + inbox_serialised) => { + return addEmailService(app, serviceInfo, inbox_serialised) + .then((md) => md.getNameAndTag()) + .then((services) => app.mutableData.newMutation() + .then((mut) => mut.insert(serviceInfo.publicId, services.name) + .then(() => pub_names_md.applyEntriesMutation(mut)) + )) +} + +export const setupAccount = (app, emailId) => { + let newAccount = {}; + let inbox_serialised; + let inbox; + let key_pair = genKeyPair(); + let serviceInfo = genServiceInfo(emailId); + + return createInbox(app, key_pair.publicKey) + .then((md) => inbox = md) + .then(() => createArchive(app)) + .then((md) => newAccount = {id: serviceInfo.emailId, inbox_md: inbox, archive_md: md, + enc_sk: key_pair.privateKey, enc_pk: key_pair.publicKey}) + .then(() => newAccount.inbox_md.serialise()) + .then((md_serialised) => inbox_serialised = md_serialised) + .then(() => app.auth.refreshContainerAccess()) + .then(() => app.auth.getAccessContainerInfo('_publicNames')) + .then((pub_names_md) => pub_names_md.encryptKey(serviceInfo.publicId) + .then((encrypted_publicId) => pub_names_md.get(encrypted_publicId)) + .then((services) => addService(app, serviceInfo, inbox_serialised) + , (err) => { + if (err.name === 'ERR_NO_SUCH_ENTRY') { + return createPublicIdAndEmailService(app, pub_names_md, + serviceInfo, inbox_serialised); + } + throw err; + }) + ) + .then(() => newAccount); +} + +const writeEmailContent = (app, email, pk) => { + const encryptedEmail = encrypt(JSON.stringify(email), pk); + + return app.immutableData.create() + .then((email) => email.write(encryptedEmail) + .then(() => email.close()) + ); +} + +export const storeEmail = (app, email, to) => { + let serviceInfo = genServiceInfo(to); + return app.mutableData.newPublic(serviceInfo.serviceAddr, CONSTANTS.TAG_TYPE_DNS) + .then((md) => md.get(serviceInfo.serviceName)) + .catch((err) => {throw MESSAGES.EMAIL_ID_NOT_FOUND}) + .then((service) => app.mutableData.fromSerial(service.buf)) + .then((inbox_md) => inbox_md.get(CONSTANTS.MD_KEY_EMAIL_ENC_PUBLIC_KEY) + .then((pk) => writeEmailContent(app, email, pk.buf.toString()) + .then((email_addr) => app.mutableData.newMutation() + .then((mut) => { + let entry_value = encrypt(email_addr.buffer.toString('hex'), pk.buf.toString()); + let entry_key = genRandomEntryKey(); + return mut.insert(entry_key, entry_value) + .then(() => inbox_md.applyEntriesMutation(mut)) + }) + ))); +} + +export const removeInboxEmail = (app, account, key) => { + return app.mutableData.newMutation() + .then((mut) => mut.remove(key, 1) + .then(() => account.inbox_md.applyEntriesMutation(mut)) + ) +} + +export const removeArchivedEmail = (app, account, key) => { + return app.mutableData.newMutation() + .then((mut) => account.archive_md.encryptKey(key) + .then((encryptedKey) => mut.remove(encryptedKey, 1)) + .then(() => account.archive_md.applyEntriesMutation(mut)) + ) +} + +export const archiveEmail = (app, account, key) => { + let new_entry_key = genRandomEntryKey(); + return account.inbox_md.get(key) + .then((encryptedEmailXorName) => decrypt(encryptedEmailXorName.buf.toString(), account.enc_sk, account.enc_pk)) + .then((decrytedXorNameHex) => Buffer.from(decrytedXorNameHex, 'hex')) + .then((xorName) => app.mutableData.newMutation() + .then((mut) => mut.insert(new_entry_key, xorName) + .then(() => account.archive_md.applyEntriesMutation(mut)) + ) + ) + .then(() => app.mutableData.newMutation()) + .then((mut) => mut.remove(key, 1) + .then(() => account.inbox_md.applyEntriesMutation(mut)) + ) +} diff --git a/email_app/app/utils/app_utils.js b/email_app/app/utils/app_utils.js index 63c8499..7722cee 100644 --- a/email_app/app/utils/app_utils.js +++ b/email_app/app/utils/app_utils.js @@ -2,6 +2,7 @@ import crypto from 'crypto'; import * as base64 from 'urlsafe-base64'; import { remote } from 'electron'; import { CONSTANTS } from '../constants'; +import sodium from 'libsodium-wrappers'; export const getAuthData = () => { let authData = window.JSON.parse( @@ -10,34 +11,45 @@ export const getAuthData = () => { return authData; }; -export const setAuthData = (authData) => { +export const saveAuthData = (authData) => { return window.localStorage.setItem(CONSTANTS.LOCAL_AUTH_DATA_KEY, window.JSON.stringify(authData) ); }; -export const checkAuthorised = (nextState, replace, callback) => { - let authData = getAuthData(); - if (!authData) { - replace('/create_account'); - } - callback(); +export const clearAuthData = () => { + window.localStorage.removeItem(CONSTANTS.LOCAL_AUTH_DATA_KEY); }; -export const hashEmailId = emailId => { - return crypto.createHash('sha256').update(emailId).digest('base64'); +export const genServiceInfo = (emailId) => { + // It supports complex email IDs, e.g. 'emailA.myshop', 'emailB.myshop' + let str = emailId.replace(/\.+$/, ''); + let toParts = str.split('.'); + const publicId = toParts.pop(); + const serviceId = str.slice(0, -1 * (publicId.length+1)); + emailId = (serviceId.length > 0 ? (serviceId + '.') : '') + publicId; + const serviceName = serviceId + CONSTANTS.SERVICE_NAME_POSTFIX; + const serviceAddr = hashPublicId(publicId); + return {emailId, publicId, serviceAddr, serviceName}; +} + +export const hashPublicId = publicId => { + return crypto.createHash('sha256').update(publicId).digest(); +}; + +export const genRandomEntryKey = () => { + return crypto.randomBytes(32).toString('hex'); }; -export const showError = (title, errMsg) => { +export const showError = (title, errMsg, next) => { remote.dialog.showMessageBox({ type: 'error', buttons: ['Ok'], title, - message: errMsg - }, _ => {}); + message: errMsg.toString() + }, next ? next : _ => {}); }; - export const showSuccess = (title, message) => { remote.dialog.showMessageBox({ type: 'info', @@ -46,3 +58,16 @@ export const showSuccess = (title, message) => { message }, _ => {}); }; + +export const genKeyPair = () => { + let {keyType, privateKey, publicKey} = sodium.crypto_box_keypair('hex'); + return {privateKey, publicKey}; +} + +export const encrypt = (input, pk) => sodium.crypto_box_seal(input, Buffer.from(pk, 'hex'), 'hex'); + +export const decrypt = (cipherMsg, sk, pk) => sodium.crypto_box_seal_open( + Buffer.from(cipherMsg, 'hex'), + Buffer.from(pk, 'hex'), + Buffer.from(sk, 'hex'), + 'text'); diff --git a/email_app/design/EmailApp-DataModel.png b/email_app/design/EmailApp-DataModel.png new file mode 100644 index 0000000..708c3b6 Binary files /dev/null and b/email_app/design/EmailApp-DataModel.png differ diff --git a/email_app/design/EmailApp-DataModel.xml b/email_app/design/EmailApp-DataModel.xml new file mode 100644 index 0000000..4cb1627 --- /dev/null +++ b/email_app/design/EmailApp-DataModel.xml @@ -0,0 +1 @@ +7Vzdc6o4FP9rnO59sBM+hcdW294+9M7OdGf2vjkBgrIFwga0un/9Bggf4UPRWosWHxBPQnLOycnJOb8ER9LU2zwRGCxfsIXckQiszUiajURRBLJKv2LKllFUOSUsiGOlJKEgvDr/IUYEjLpyLBRyFSOM3cgJeKKJfR+ZEUeDhOB3vpqNXb7XAC5Yj6AgvJrQzfi4VQr6344VLVO6JqoF/SdyFsusb0HV0xIDmm8Lglc+63EkSnbySYs9mLXFeg6X0MLvJZL0MJKmBOMovfM2U+TG2s0Ulz0XbTNuR9L9MvJc+kOgt0nxY8vDQpeHqXQE+VG5u9b2AGNnDd1V1qKouvTh+4DrSP13FYt070GycPyRdEdL5WBDr6B0TVgAEdpEY+g6C1bPpNwgUrRB7xbsO+nJaOwpacVCJiYwcjBrio4LIq7jo3prz76BN0mttFUqtVH0BP5cGa5j0puXVQQNF81gBEtVgypTS1Kl7FdIWQkpfewiO0oLtbiwTQU2pgNGLR2afOtTvCJOrDvwC703yDxL+kuuogq9gNKS9gj0LezlNK4vKm3anVol15TQQ5GjbYDmEVzkggsKANJ1yBYg4jlhSK09LMZVuZKBy43RN8Ig7bdOyh569kNEonIj9cpJ35x9gzt/ixPfcA36UmbXIQj1/rT4u1v0fI486Lhz5Jvz4O1gy+Z6LzNtYhcTjmsasgD6iUOWHStuSpgYgqxOJiJUoK0qgq6LigIlQ1YMw5Jt0RJ0RYUT0dQUfWJDJFimolnA1BVTEAV7Up5qRsMoVgY352baMwMIA+g3Nh8zN7ah57jbtJl8YKiWJZl+m7mN+ImN8KXHGk33pTxlvT/i1G23JlxPWKczkWyD6I9a8LSHRedM/D17XjlUBb8x+QW98pRz+jDwmYf6wU/rJs0N03qY1sO0vrhp/W3nNBeB7xe+As2saQbjmNC9S5GQWYTj7hguMkvFvce0lu0mCJLtuC6Tg0Fqgsh+PzKxZj+Ru0ZxqyMO93Ghgdz7HLqaspjQj5MhqrKI4Dc0zQNFSaWfx8ec5zJexBCmmHe0KZEYfvSEsIcisqVVGDY4AWL6yHuBs6kqg5WWZYxN1xjCx9C9Rd5WgVDRGwZStQFWwgcAqwarSwalALPSsjYAq1x8ADKVMZhHyD+p4FM6rJBWJyVbagqhLxOY2pHzGTX+e5IEnCMLTHNAas0787/OpE6JYs2qdmeOug5AXUpvm7AutIzZbZe2TZM6hsa2x0GCzo4TxeyxibI4vU0oj7el0iJ3vH0lUHjNEHbGZgd01mZ3pVgrRMShDjREdEDBTcLPDb17qUeR3TnrkF0MQw+JuXTW/CL0kcE/cKRZ981jvWMAO4g5jDEPIoYVEPEkGVrds3Z+SFQETVNtVaERngF06u4tDQq2rEJJBool6ppliZqENKCqmgY0GyoatGRdlAXNsDTts/kbHMgBxlVFqL/auCq6uSTQ+yx6aQyqr9DGD9wT+14ZOSsdqxpLv7f5KQ/QMUdXpFPk6PLVHCq5Y+FU+7ES4qxhhEbDuZILdy2N50rk65Ct/zvww07NN9yp4dW8h69he4ZTWceUpjEuvASXxT9+2pl79Hxp8pSN6VZHg/6gxTWbxCWE/8Mm3slThlp+0JBFtG7i6Uo9QRD1pgRBAqdIEJSrSRAeYsikNT3gPUllovclR/iisxmTfb7q69euHQjUJ+wS2oSuVbyeO+P+wi2OloiwfbRnbn+xw95Yfw52nEvZ4cr4J3n1iOm7rMxYSFZ8eWo8L3ocOR46wmZfYZQoFCTLwF0Qs0Uz7kmcdlPPcSeL8UWnP59e/mobhAEbPcUIGtiqevfOXoetuocM0NWd/pjEgMqPI4SxnHUn5XD1vld0yh6QBHCraqo4kRWga0DSOXS7KXaVZP3TYtfJ1cSuN/M0XIizzTDesr+QM2lnDVh3Iaf9jFrPcY6t0tdBx71KRpwSWs5/NVle2wtFnRavvWdZcuQFYJtebsqMxfMjRGTtmIk5mKWp0orI9WFd6x2yzh4q5ytj/kBk7/Dpow2IE/K5sw31KqHgeBjAtCP235uOyCtyE7oGThKhaNcToRQeOJ09r9ns+bb/8xAuoaio7O2misusrZhfdBr7qHyoh2pvPBPQdgT+wmQ77r8merxx01tN9//0RV8yhraXT2r+reFtlA7ZQsVHyoCBWLud2aExYOdXH4ZMoZvlvL/zttfX7OAjL3J9yms3VHFHv3RzJhX28x8DhmTn+GRHy1DWMhzbeJQgw20PSHboz+Kv9ZKyp+IfDKWH/wE= \ No newline at end of file diff --git a/email_app/package.json b/email_app/package.json index 9105f9c..3c6be58 100644 --- a/email_app/package.json +++ b/email_app/package.json @@ -1,10 +1,10 @@ { "name": "safe-mail-tutorial", - "productName": "SAFE Mail Tutorial", - "version": "0.1.2", + "productName": "SAFE-Mail-Tutorial", + "version": "0.2.0", "description": "Mailing application tutorial using SAFE Network", - "identifier": "net.maidsafe.mailtutorial", - "vendor": "MaidSafe Ltd.", + "identifier": "net.maidsafe.examples.mailtutorial", + "vendor": "MaidSafe", "main": "app/index.js", "scripts": { "test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --compilers js:babel-register --recursive --require ./test/setup.js test/**/*.spec.js", @@ -28,7 +28,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/shankar2105/safe_mail_app.git" + "url": "git+https://github.com/maidsafe/safe_examples.git" }, "author": { "name": "MaidSafe", @@ -37,12 +37,12 @@ }, "license": "MIT", "bugs": { - "url": "https://github.com/shankar2105/safe_mail_app/issues" + "url": "https://github.com/maidsafe/safe_examples/issues" }, "keywords": [ "maidsafe" ], - "homepage": "https://github.com/shankar2105/safe_mail_app#readme", + "homepage": "https://github.com/maidsafe/safe_examples/blob/master/email_app/README.md", "devDependencies": { "asar": "^0.12.2", "babel-core": "^6.14.0", @@ -61,7 +61,6 @@ "babel-preset-stage-0": "^6.5.0", "babel-register": "^6.14.0", "chai": "^3.5.0", - "classnames": "^2.2.5", "concurrently": "^2.2.0", "cross-env": "^2.0.0", "css-loader": "^0.24.0", @@ -102,10 +101,13 @@ "axios": "^0.14.0", "babel-plugin-transform-class-properties": "^6.23.0", "buffer-stream-reader": "^0.1.1", + "classnames": "^2.2.5", "css-modules-require-hook": "^4.0.2", "dateformat": "^1.0.12", - "electron-compile": "^6.1.3", + "electron-compile": "^6.2.0", "electron-debug": "^1.0.1", + "less": "^2.7.2", + "libsodium-wrappers": "^0.5.1", "material-design-lite": "^1.2.1", "open-sans-fontface": "^1.4.0", "postcss": "^5.1.2", @@ -141,7 +143,12 @@ "rpm" ] }, - "electronPackagerConfig": {}, + "electronPackagerConfig": { + "name": "safe-mail-tutorial", + "appBundleId": "net.maidsafe.examples.mailtutorial", + "appCategoryType": "public.app-category.tools", + "dir": "./" + }, "electronWinstallerConfig": { "name": "" }, diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/AppController.java b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/AppController.java index 89cbf02..0d2704c 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/AppController.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/AppController.java @@ -3,8 +3,8 @@ import java.util.List; import net.maidsafe.example.mail.dao.IMessagingDao; import net.maidsafe.example.mail.dao.LauncherAPI; -import net.maidsafe.example.mail.modal.Message; -import net.maidsafe.example.mail.modal.Result; +import net.maidsafe.example.mail.model.Message; +import net.maidsafe.example.mail.model.Result; import net.maidsafe.example.mail.util.ServiceHandler; /** diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/IdCreationController.java b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/IdCreationController.java index f1560d2..7ef4589 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/IdCreationController.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/IdCreationController.java @@ -2,7 +2,7 @@ import net.maidsafe.example.mail.dao.IMessagingDao; import net.maidsafe.example.mail.dao.LauncherAPI; -import net.maidsafe.example.mail.modal.Result; +import net.maidsafe.example.mail.model.Result; import net.maidsafe.example.mail.util.ServiceHandler; /** diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/InitialisationController.java b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/InitialisationController.java index 1960692..c356fef 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/controller/InitialisationController.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/controller/InitialisationController.java @@ -2,7 +2,7 @@ import net.maidsafe.example.mail.dao.IMessagingDao; import net.maidsafe.example.mail.dao.LauncherAPI; -import net.maidsafe.example.mail.modal.Result; +import net.maidsafe.example.mail.model.Result; import net.maidsafe.example.mail.util.ServiceHandler; /** diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IMessagingDao.java b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IMessagingDao.java index 7ce40ae..a2b74b1 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IMessagingDao.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IMessagingDao.java @@ -2,8 +2,8 @@ import java.util.List; import java.util.concurrent.Future; -import net.maidsafe.example.mail.modal.Message; -import net.maidsafe.example.mail.modal.Result; +import net.maidsafe.example.mail.model.Message; +import net.maidsafe.example.mail.model.Result; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IRestAPI.java b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IRestAPI.java index cf28266..3f5b506 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IRestAPI.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/IRestAPI.java @@ -1,17 +1,17 @@ package net.maidsafe.example.mail.dao; -import net.maidsafe.example.mail.modal.AppConfig; -import net.maidsafe.example.mail.modal.AppendableDataCreateRequest; -import net.maidsafe.example.mail.modal.AppendableDataDataId; -import net.maidsafe.example.mail.modal.AppendableDataMetadata; -import net.maidsafe.example.mail.modal.AuthRequest; -import net.maidsafe.example.mail.modal.AuthResponse; -import net.maidsafe.example.mail.modal.HandleIdResponse; -import net.maidsafe.example.mail.modal.ReaderInfo; -import net.maidsafe.example.mail.modal.StructuredDataCreateRequest; -import net.maidsafe.example.mail.modal.StructuredDataDataId; -import net.maidsafe.example.mail.modal.StructuredDataMetadata; -import net.maidsafe.example.mail.modal.StructuredDataUpdateRequest; +import net.maidsafe.example.mail.model.AppConfig; +import net.maidsafe.example.mail.model.AppendableDataCreateRequest; +import net.maidsafe.example.mail.model.AppendableDataDataId; +import net.maidsafe.example.mail.model.AppendableDataMetadata; +import net.maidsafe.example.mail.model.AuthRequest; +import net.maidsafe.example.mail.model.AuthResponse; +import net.maidsafe.example.mail.model.HandleIdResponse; +import net.maidsafe.example.mail.model.ReaderInfo; +import net.maidsafe.example.mail.model.StructuredDataCreateRequest; +import net.maidsafe.example.mail.model.StructuredDataDataId; +import net.maidsafe.example.mail.model.StructuredDataMetadata; +import net.maidsafe.example.mail.model.StructuredDataUpdateRequest; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.Call; diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/LauncherAPI.java b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/LauncherAPI.java index e90689f..dcb82a7 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/dao/LauncherAPI.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/dao/LauncherAPI.java @@ -12,20 +12,20 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import net.maidsafe.example.mail.modal.AppConfig; -import net.maidsafe.example.mail.modal.AppInfo; -import net.maidsafe.example.mail.modal.AppendableDataCreateRequest; -import net.maidsafe.example.mail.modal.AppendableDataDataId; -import net.maidsafe.example.mail.modal.AppendableDataMetadata; -import net.maidsafe.example.mail.modal.AuthRequest; -import net.maidsafe.example.mail.modal.AuthResponse; -import net.maidsafe.example.mail.modal.FileResponse; -import net.maidsafe.example.mail.modal.HandleIdResponse; -import net.maidsafe.example.mail.modal.Message; -import net.maidsafe.example.mail.modal.Result; -import net.maidsafe.example.mail.modal.StructuredDataCreateRequest; -import net.maidsafe.example.mail.modal.StructuredDataDataId; -import net.maidsafe.example.mail.modal.StructuredDataUpdateRequest; +import net.maidsafe.example.mail.model.AppConfig; +import net.maidsafe.example.mail.model.AppInfo; +import net.maidsafe.example.mail.model.AppendableDataCreateRequest; +import net.maidsafe.example.mail.model.AppendableDataDataId; +import net.maidsafe.example.mail.model.AppendableDataMetadata; +import net.maidsafe.example.mail.model.AuthRequest; +import net.maidsafe.example.mail.model.AuthResponse; +import net.maidsafe.example.mail.model.FileResponse; +import net.maidsafe.example.mail.model.HandleIdResponse; +import net.maidsafe.example.mail.model.Message; +import net.maidsafe.example.mail.model.Result; +import net.maidsafe.example.mail.model.StructuredDataCreateRequest; +import net.maidsafe.example.mail.model.StructuredDataDataId; +import net.maidsafe.example.mail.model.StructuredDataUpdateRequest; import okhttp3.Headers; import okhttp3.HttpUrl; @@ -52,7 +52,7 @@ public class LauncherAPI implements IMessagingDao, Cloneable { private final String END_POINT = "http://localhost:8100/"; private IRestAPI api; - // Hanldes + // Handles private Long symmetricCipherOptsHandle; private long structuredDataHandle; private List savedMessagesDataIdList; @@ -195,7 +195,7 @@ public Future> createID(String id) { appendableDataHandle = res.body().getHandleId(); // Invoke PUT endpoint for creating AD api.saveAppendableData(authToken, appendableDataHandle).execute(); - // Create SD for saved messages bby passing symmetric cipher opts handle + // Create SD for saved messages by passing symmetric cipher opts handle sdCreateReq = new StructuredDataCreateRequest(appConfig.getStructuredDataName(), UNVERSIONED_SD_TAG, symmetricCipherOptsHandle, Base64.getEncoder().encodeToString("[]".getBytes())); structuredDataHandle = api.createStructuredData(authToken, sdCreateReq).execute().body().getHandleId(); api.saveStructuredData(authToken, structuredDataHandle).execute(); @@ -296,9 +296,9 @@ public Future>> fetchSavedMessages() { * Invoked to send a message The recipient's appendable data is fetched and * the encrypt key is obtained The message is written to the network as * ImmutableData and encrypted using the receiver's encrypt key (Asymmetric - * encryption) - * The DataId of the ImmutableData is added to the receiver's appendable data - * + * encryption) The DataId of the ImmutableData is added to the receiver's + * appendable data + * * @param message * @return */ @@ -309,7 +309,7 @@ public Future> sendMessage(Message message) { String toName; long toDataId; long toAppendableData; - long dataIdForMessage; + long dataIdForMessage; toName = getBase64Name(message.getTo()); toDataId = api.getDataId(authToken, new AppendableDataDataId(toName, true)).execute().body().getHandleId(); @@ -318,7 +318,7 @@ public Future> sendMessage(Message message) { return new Result(false, "Failed to get appendable data for recepient"); } toAppendableData = res.body().getHandleId(); - + long encryptKeyHandle = api.getEncryptKey(authToken, toAppendableData).execute().body().getHandleId(); long cipherHandle = api.getAsymmetricCipherOptHandle(authToken, encryptKeyHandle).execute().body().getHandleId(); long writerHandle = api.getImmutableDataWritter(authToken).execute().body().getHandleId(); @@ -337,20 +337,20 @@ public Future> sendMessage(Message message) { api.dropImmutableDataWriter(authToken, writerHandle).execute(); api.dropDataId(authToken, dataIdForMessage).execute(); api.dropDataId(authToken, toDataId).execute(); - api.dropAppendableDataHandle(authToken, toAppendableData); + api.dropAppendableDataHandle(authToken, toAppendableData); return new Result(true, true); }); } /** - * Save the message from inbox to the Structured Data - * Fetch the DataId of the message from the appendable data - * Read the message and save it as symmetric encrypted ImmutableData - * The DataId of the ImmutableData is serialised and stored in the SD - * The message is also removed from the AppendableData - * + * Save the message from inbox to the Structured Data Fetch the DataId of + * the message from the appendable data Read the message and save it as + * symmetric encrypted ImmutableData The DataId of the ImmutableData is + * serialised and stored in the SD The message is also removed from the + * AppendableData + * * @param message - * @return + * @return */ @Override public Future> saveMessage(Message message) { diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppConfig.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppConfig.java similarity index 94% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppConfig.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AppConfig.java index e0f8d65..83b438a 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppConfig.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppConfig.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * The information needed by the application to bootstrap is stored in the config file diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppInfo.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppInfo.java similarity index 94% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppInfo.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AppInfo.java index 260a287..173e4bc 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppInfo.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppInfo.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataCreateRequest.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataCreateRequest.java similarity index 91% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataCreateRequest.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataCreateRequest.java index db3f987..fe0403c 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataCreateRequest.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataCreateRequest.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataDataId.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataDataId.java similarity index 90% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataDataId.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataDataId.java index ce049a0..83171e8 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataDataId.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataDataId.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataMetadata.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataMetadata.java similarity index 97% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataMetadata.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataMetadata.java index ed2430d..c4b60ae 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AppendableDataMetadata.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AppendableDataMetadata.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthRequest.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthRequest.java similarity index 91% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthRequest.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthRequest.java index 89412cf..75651c1 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthRequest.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthRequest.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; import java.util.ArrayList; import java.util.List; diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthResponse.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthResponse.java similarity index 92% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthResponse.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthResponse.java index 24cfcd9..532839b 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/AuthResponse.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/AuthResponse.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; import java.util.List; diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/FileResponse.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/FileResponse.java similarity index 93% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/FileResponse.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/FileResponse.java index 8027459..d11e8ca 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/FileResponse.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/FileResponse.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/HandleIdResponse.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/HandleIdResponse.java similarity index 86% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/HandleIdResponse.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/HandleIdResponse.java index be8f747..fab9c8d 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/HandleIdResponse.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/HandleIdResponse.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/Message.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/Message.java similarity index 96% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/Message.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/Message.java index e39d3bb..980f2be 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/Message.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/Message.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; import java.util.Date; diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/ReaderInfo.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/ReaderInfo.java similarity index 90% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/ReaderInfo.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/ReaderInfo.java index 59fcbe2..7578a2b 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/ReaderInfo.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/ReaderInfo.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/Result.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/Result.java similarity index 93% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/Result.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/Result.java index dcf1b7d..a894d5a 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/Result.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/Result.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataCreateRequest.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataCreateRequest.java similarity index 96% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataCreateRequest.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataCreateRequest.java index e7fb092..6211681 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataCreateRequest.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataCreateRequest.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataDataId.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataDataId.java similarity index 90% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataDataId.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataDataId.java index 0b4d00c..e8f578d 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataDataId.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataDataId.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataMetadata.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataMetadata.java similarity index 95% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataMetadata.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataMetadata.java index cf25a27..2affdc9 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataMetadata.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataMetadata.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataUpdateRequest.java b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataUpdateRequest.java similarity index 91% rename from email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataUpdateRequest.java rename to email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataUpdateRequest.java index a40ebd7..bb60659 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/modal/StructuredDataUpdateRequest.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/model/StructuredDataUpdateRequest.java @@ -1,4 +1,4 @@ -package net.maidsafe.example.mail.modal; +package net.maidsafe.example.mail.model; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/scene/HomeScene.java b/email_app_java/src/main/java/net/maidsafe/example/mail/scene/HomeScene.java index 646cb01..d891308 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/scene/HomeScene.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/scene/HomeScene.java @@ -28,7 +28,7 @@ import javafx.scene.text.FontWeight; import javafx.stage.Stage; import net.maidsafe.example.mail.controller.AppController; -import net.maidsafe.example.mail.modal.Message; +import net.maidsafe.example.mail.model.Message; /** * diff --git a/email_app_java/src/main/java/net/maidsafe/example/mail/util/ServiceHandler.java b/email_app_java/src/main/java/net/maidsafe/example/mail/util/ServiceHandler.java index 66d6f42..5f47518 100644 --- a/email_app_java/src/main/java/net/maidsafe/example/mail/util/ServiceHandler.java +++ b/email_app_java/src/main/java/net/maidsafe/example/mail/util/ServiceHandler.java @@ -4,7 +4,7 @@ import javafx.application.Platform; import javafx.concurrent.Service; import javafx.concurrent.Task; -import net.maidsafe.example.mail.modal.Result; +import net.maidsafe.example.mail.model.Result; /** * Blocking operations are threaded and once the result is obtained the diff --git a/markdown_editor/src/store.js b/markdown_editor/src/store.js index fbdb0c6..f08ea70 100644 --- a/markdown_editor/src/store.js +++ b/markdown_editor/src/store.js @@ -51,16 +51,7 @@ const _refreshConfig = () => { }; const getSDHandle = (filename) => { - let dataIdHandle = null; - return safeDataId.getStructuredDataHandle(ACCESS_TOKEN, btoa(`${USER_PREFIX}:${filename}`), 501) - .then(extractHandle) - .then(handleId => (dataIdHandle = handleId)) - .then(() => safeStructuredData.getHandle(ACCESS_TOKEN, dataIdHandle)) - .then(extractHandle) - .then(handleId => { - safeDataId.dropHandle(ACCESS_TOKEN, dataIdHandle); - return handleId; - }) + return Promise.resolve(FILE_INDEX[filename]); }; const updateFile = (filename, payload) => { diff --git a/web_hosting_manager/README.md b/web_hosting_manager/README.md index 9693b42..5041c9a 100644 --- a/web_hosting_manager/README.md +++ b/web_hosting_manager/README.md @@ -1,5 +1,8 @@ # SAFE Hosting Manager +#### Prerequisites +> SAFE Hosting Manager uses **[keytar](https://www.npmjs.com/package/keytar)** module as its dependency. Please install the prerequisites mentioned [here](https://www.npmjs.com/package/keytar#installing) based on the platform. + ## Install * **Note: requires a node version 6.5.0 and an npm version 3.10.3** @@ -7,17 +10,20 @@ First, clone the repo via git: ```bash -git clone https://github.com/maidsafe/safe_examples && cd safe_examples/web_hosting_manager +$ git clone https://github.com/maidsafe/safe_examples && cd safe_examples/web_hosting_manager ``` -And then install dependencies. -Install with [yarn](https://github.com/yarnpkg/yarn) for faster and safer installation +And then install Node.js dependencies. ```bash -yarn install +$ npm i ``` -Manually build [safe_app_nodejs](https://github.com/maidsafe/safe_app_nodejs) dependency from `app/node_modules/safe-app` +Finally, rebuild the native modules + +```bash +$ npm run rebuild +``` ## Run @@ -34,36 +40,8 @@ or run two servers with one command $ npm run dev ``` -## CSS Modules - -This boilerplate out of the box is configured to use [css-modules](https://github.com/css-modules/css-modules) and SASS. - -All `.scss` file extensions will use css-modules unless it has `.global.scss`. - -If you need global styles, stylesheets with `.global.scss` will not go through the -css-modules loader. e.g. `app.global.scss` - -If you want to import global css libraries (like `bootstrap`), you can just write the following code in `.global.scss`: - -```css -@import "~bootstrap/dist/css/bootstrap.css"; -``` - -For SASS mixin -```css -@import "~bootstrap/dist/css/bootstrap.css"; -``` - - ## Packaging -Based on the platform configure `build.asarUnpack` option in package.json -``` -osx : "*.dylib" -linux : "*.so" -windows: "*.dll" -``` - To package apps for the local platform: ```bash @@ -72,6 +50,10 @@ $ npm run package To package apps for all platforms: +```bash +$ npm run package-all +``` + To package apps with options: ```bash @@ -87,17 +69,18 @@ $ npm run build $ npm start ``` -To run End-to-End Test +# License -```bash -$ npm run build -$ npm run test-e2e -``` +Licensed under either of + +* the MaidSafe.net Commercial License, version 1.0 or later ([LICENSE](LICENSE)) +* the General Public License (GPL), version 3 ([COPYING](COPYING) or http://www.gnu.org/licenses/gpl-3.0.en.html) -#### Module Structure +at your option. -This boilerplate uses a [two package.json structure](https://github.com/electron-userland/electron-builder#two-packagejson-structure). +# Contribution -1. If the module is native to a platform or otherwise should be included with the published package (i.e. bcrypt, openbci), it should be listed under `dependencies` in `./app/package.json`. -2. If a module is `import`ed by another module, include it in `dependencies` in `./package.json`. See [this ESLint rule](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md). -3. Otherwise, modules used for building, testing and debugging should be included in `devDependencies` in `./package.json`. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the MaidSafe Contributor Agreement, version 1.1 ([CONTRIBUTOR] +(CONTRIBUTOR)), shall be dual licensed as above, and you agree to be bound by the terms of the +MaidSafe Contributor Agreement, version 1.1. diff --git a/web_hosting_manager/app/components/CreateService.js b/web_hosting_manager/app/components/CreateService.js index 14a951f..ca8333b 100644 --- a/web_hosting_manager/app/components/CreateService.js +++ b/web_hosting_manager/app/components/CreateService.js @@ -121,7 +121,7 @@ export default class CreateService extends Component {
    - +