diff --git a/.eslintrc.js b/.eslintrc.js
index 4bce742..c0d8498 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,86 +1,76 @@
const {resolve} = require('./webpack/base');
-const INDENT = 4;
-
const base = {
- // disabled
- 'max-len': 'off',
- 'arrow-parens': 'off',
- 'arrow-body-style': 'off',
- 'no-prototype-builtins': 'off',
- 'no-plusplus': 'off',
+ // disabled
+ 'max-len': 'off',
+ 'arrow-parens': 'off',
+ 'arrow-body-style': 'off',
+ 'no-prototype-builtins': 'off',
+ 'no-plusplus': 'off',
- // modified
- indent: ['error', INDENT, {
- SwitchCase: 1
- }],
- 'brace-style': ['error', 'stroustrup', {
- 'allowSingleLine': false
- }],
- 'object-curly-spacing': ['error', 'never'],
- 'one-var': ['error', {
- initialized: 'never'
- }],
- 'one-var-declaration-per-line': ['error', 'initializations'],
+ // modified
+ 'object-curly-spacing': ['error', 'never'],
+ 'one-var': ['error', {
+ initialized: 'never'
+ }],
+ 'one-var-declaration-per-line': ['error', 'initializations'],
};
const react = {
- // disabled
- 'react/forbid-prop-types': 'off',
- 'react/no-array-index-key': 'off',
+ // disabled
+ 'react/forbid-prop-types': 'off',
+ 'react/no-array-index-key': 'off',
- // modified
- 'react/jsx-filename-extension': ['error', {extensions: ['.js']}],
- 'react/jsx-indent': ['error', INDENT],
- 'react/jsx-indent-props': ['error', INDENT],
- 'react/jsx-tag-spacing': ['error', {
- closingSlash: 'never',
- beforeSelfClosing: 'never',
- afterOpening: 'never'
- }],
- 'react/sort-comp': ['error', {
- order: [
- 'static-methods',
- 'lifecycle',
- 'rendering',
- 'everything-else'
- ],
- groups: {
- rendering: [
- 'render',
- '/^render.+$/'
- ]
- }
- }],
+ // modified
+ 'react/jsx-filename-extension': ['error', {extensions: ['.js']}],
+ 'react/jsx-tag-spacing': ['error', {
+ closingSlash: 'never',
+ beforeSelfClosing: 'never',
+ afterOpening: 'never'
+ }],
+ 'react/sort-comp': ['error', {
+ order: [
+ 'static-methods',
+ 'lifecycle',
+ 'rendering',
+ 'everything-else'
+ ],
+ groups: {
+ rendering: [
+ 'render',
+ '/^render.+$/'
+ ]
+ }
+ }],
};
const other = {
- 'jsx-a11y/no-static-element-interactions': 'off'
+ 'jsx-a11y/no-static-element-interactions': 'off'
};
module.exports = {
- parser: 'babel-eslint',
- extends: 'airbnb',
- env: {
- webextensions: true,
- browser: true,
- jest: true
- },
- globals: {
- __DEV__: false
- },
- settings: {
- 'import/resolver': {
- webpack: {
- config: {
- resolve
- }
- }
+ parser: 'babel-eslint',
+ extends: 'airbnb',
+ env: {
+ webextensions: true,
+ browser: true,
+ jest: true
+ },
+ globals: {
+ __DEV__: false
+ },
+ settings: {
+ 'import/resolver': {
+ webpack: {
+ config: {
+ resolve
}
- },
- rules: Object.assign({},
- base,
- react,
- other
- )
+ }
+ }
+ },
+ rules: {
+ ...base,
+ ...react,
+ ...other,
+ }
};
diff --git a/.travis.yml b/.travis.yml
index 6cf8a5f..26d4277 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,11 @@
language: node_js
node_js:
- 8
+before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.1.0 # otherwise Travis uses 0.27.*
+ - export PATH=$HOME/.yarn/bin:$PATH
script:
+ - set -e # fail the build if one of the scripts fails - https://github.com/travis-ci/travis-ci/issues/1066
- npm run lint
- npm run test:coverage
cache:
@@ -13,4 +17,4 @@ deploy:
script: npm run publish
skip_cleanup: true
on:
- tags: true
\ No newline at end of file
+ tags: true
diff --git a/jest.config.js b/jest.config.js
index cabd2dd..bd8bcf9 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,16 +1,16 @@
module.exports = {
- rootDir: './src',
- clearMocks: true,
- resetModules: true,
- moduleDirectories: [
- 'node_modules',
- 'src/pages',
- ],
- moduleNameMapper: {
- '\\.(less)$': 'identity-obj-proxy',
- },
- testPathIgnorePatterns: [
- '/node_modules/',
- 'fixtures.js',
- ],
+ rootDir: './src',
+ clearMocks: true,
+ resetModules: true,
+ moduleDirectories: [
+ 'node_modules',
+ 'src/pages',
+ ],
+ moduleNameMapper: {
+ '\\.(less)$': 'identity-obj-proxy',
+ },
+ testPathIgnorePatterns: [
+ '/node_modules/',
+ 'fixtures.js',
+ ],
};
diff --git a/package.json b/package.json
index c530bab..1e41ba9 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"copy-webpack-plugin": "^4.0.0",
"css-loader": "^0.28.7",
"date-fns": "^1.28.5",
- "eslint": "^3.19.0",
+ "eslint": "^4.8.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-import": "^2.7.0",
diff --git a/scripts/deploy.js b/scripts/deploy.js
index bbcdffb..86c0742 100644
--- a/scripts/deploy.js
+++ b/scripts/deploy.js
@@ -6,73 +6,73 @@ const clientId = process.env.CLIENT_ID;
const refreshToken = process.env.REFRESH_TOKEN;
function getError(text, err) {
- throw new Error(`Failed to ${text}: ${(err.response && err.response.text) || err}`);
+ throw new Error(`Failed to ${text}: ${(err.response && err.response.text) || err}`);
}
function getToken() {
- return request
- .post('https://accounts.google.com/o/oauth2/token')
- .type('form')
- .send({
- client_id: clientId,
- refresh_token: refreshToken,
- grant_type: 'refresh_token',
- redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
- })
- .then(res => {
- const accessToken = res.body.access_token;
+ return request
+ .post('https://accounts.google.com/o/oauth2/token')
+ .type('form')
+ .send({
+ client_id: clientId,
+ refresh_token: refreshToken,
+ grant_type: 'refresh_token',
+ redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
+ })
+ .then(res => {
+ const accessToken = res.body.access_token;
- if (!accessToken) {
- throw new Error(res.body);
- }
+ if (!accessToken) {
+ throw new Error(res.body);
+ }
- console.log('(1/3) Token received!');
+ console.log('(1/3) Token received!');
- return accessToken;
- })
- .catch(err => getError('receive access token', err));
+ return accessToken;
+ })
+ .catch(err => getError('receive access token', err));
}
function upload(accessToken) {
- return request
- .put(`https://www.googleapis.com/upload/chromewebstore/v1.1/items/${appId}`)
- .set({
- Authorization: `Bearer ${accessToken}`,
- 'x-goog-api-version': 2,
- })
- .attach('file', filename)
- .then(res => {
- if (res.body.uploadState !== 'SUCCESS') {
- throw new Error(res.body.itemError[0].error_detail);
- }
+ return request
+ .put(`https://www.googleapis.com/upload/chromewebstore/v1.1/items/${appId}`)
+ .set({
+ Authorization: `Bearer ${accessToken}`,
+ 'x-goog-api-version': 2,
+ })
+ .attach('file', filename)
+ .then(res => {
+ if (res.body.uploadState !== 'SUCCESS') {
+ throw new Error(res.body.itemError[0].error_detail);
+ }
- console.log('(2/3) Uploaded!');
+ console.log('(2/3) Uploaded!');
- return accessToken;
- })
- .catch(err => getError('upload package', err));
+ return accessToken;
+ })
+ .catch(err => getError('upload package', err));
}
function publish(accessToken) {
- return request
- .post(`https://www.googleapis.com/chromewebstore/v1.1/items/${appId}/publish`)
- .set({
- Authorization: `Bearer ${accessToken}`,
- 'x-goog-api-version': 2,
- 'Content-Length': 0,
- // publishTarget: 'trustedTesters'
- })
- .then(res => {
- if (res.body.status[0] !== 'OK') {
- throw new Error(res.body);
- }
+ return request
+ .post(`https://www.googleapis.com/chromewebstore/v1.1/items/${appId}/publish`)
+ .set({
+ Authorization: `Bearer ${accessToken}`,
+ 'x-goog-api-version': 2,
+ 'Content-Length': 0,
+ // publishTarget: 'trustedTesters'
+ })
+ .then(res => {
+ if (res.body.status[0] !== 'OK') {
+ throw new Error(res.body);
+ }
- console.log('(3/3) Published!');
- })
- .catch(err => getError('publish package', err));
+ console.log('(3/3) Published!');
+ })
+ .catch(err => getError('publish package', err));
}
getToken()
- .then(upload)
- .then(publish)
- .catch(console.log);
+ .then(upload)
+ .then(publish)
+ .catch(console.log);
diff --git a/scripts/locales.js b/scripts/locales.js
index 2761a0c..b33154e 100644
--- a/scripts/locales.js
+++ b/scripts/locales.js
@@ -1,6 +1,6 @@
const i18n = require('webext-i18n');
i18n({
- inputDir: './src/locales',
- outputDir: './dist/_locales',
+ inputDir: './src/locales',
+ outputDir: './dist/_locales',
}).then(() => console.log('Locales are generated.'));
diff --git a/scripts/release.js b/scripts/release.js
index 708fcc5..b4515af 100644
--- a/scripts/release.js
+++ b/scripts/release.js
@@ -2,30 +2,30 @@ const {execSync} = require('child_process');
const fs = require('fs-extra');
const paths = [
- './package.json',
- './src/manifest/base.json',
+ './package.json',
+ './src/manifest/base.json',
];
function validate(newVersion) {
- const semverRegex = /^\d+\.\d+\.\d+$/;
+ const semverRegex = /^\d+\.\d+\.\d+$/;
- if (!newVersion || !semverRegex.test(newVersion)) {
- throw new Error('The release version is not provided or is incorrect');
- }
+ if (!newVersion || !semverRegex.test(newVersion)) {
+ throw new Error('The release version is not provided or is incorrect');
+ }
}
function updateVersion(newVersion) {
- paths.forEach(path => {
- const config = fs.readJsonSync(path);
- config.version = newVersion;
- fs.writeJsonSync(path, config, {spaces: 2});
- });
+ paths.forEach(path => {
+ const config = fs.readJsonSync(path);
+ config.version = newVersion;
+ fs.writeJsonSync(path, config, {spaces: 2});
+ });
}
function makeCommit(newVersion) {
- execSync(`git add ${paths.join(' ')}`);
- execSync(`git commit -m "bump v${newVersion}" --no-verify`);
- execSync(`git tag v${newVersion}`);
+ execSync(`git add ${paths.join(' ')}`);
+ execSync(`git commit -m "bump v${newVersion}" --no-verify`);
+ execSync(`git tag v${newVersion}`);
}
const newVersion = process.argv[2];
diff --git a/src/locales/en.js b/src/locales/en.js
index c8d5c74..394b149 100644
--- a/src/locales/en.js
+++ b/src/locales/en.js
@@ -1,108 +1,108 @@
module.exports = {
- ext: {
- name: 'Yandex.Mail notifier',
- description: 'Tunable notifier for Yandex.Mail',
+ ext: {
+ name: 'Yandex.Mail notifier',
+ description: 'Tunable notifier for Yandex.Mail',
+ },
+ notification: {
+ unread: {
+ title: 'You have unread emails',
+ message: 'Unread emails: $1',
},
- notification: {
- unread: {
- title: 'You have unread emails',
- message: 'Unread emails: $1',
- },
- notAuth: {
- title: 'You\'re not authorized',
- message: 'Please, log in into your Yandex account',
- },
+ notAuth: {
+ title: 'You\'re not authorized',
+ message: 'Please, log in into your Yandex account',
},
- popup: {
- title: 'Yandex.Mail notifier',
- compose: 'Compose',
- actions: {
- open: 'Open',
- read: 'Mark as read',
- spam: 'Spam',
- remove: 'Remove',
- },
- emptyList: 'You have no unread emails',
- loadingError: 'Cannot load emails... Please try again later',
- unavailable: {
- title: 'Something went wrong...',
- subTitle: 'There are a few possible reasons why the extension may not work properly:',
- notAuth: {
- label: 'You are not authorized.',
- checkAuth: 'Check that you\'re authorized on Yandex (exactly .$1 domain). Also you can choose another domain in the extension settings.',
- relogin: 'Try to logout and login again.',
- },
- noConnection: {
- label: 'There is no connection to the Internet.',
- },
- nothingHelped: 'If nothing above helped, you can try to reload the extension.',
- reloadBtn: 'Reload extension',
- },
- donation: {
- text: 'Want more settings, new features and just stable work of the extension?',
- donateBtn: 'Support the project!',
- },
- months: [
- 'January',
- 'February',
- 'March',
- 'April',
- 'May',
- 'June',
- 'July',
- 'August',
- 'September',
- 'October',
- 'November',
- 'December',
- ],
+ },
+ popup: {
+ title: 'Yandex.Mail notifier',
+ compose: 'Compose',
+ actions: {
+ open: 'Open',
+ read: 'Mark as read',
+ spam: 'Spam',
+ remove: 'Remove',
},
- settings: {
- newMessageNotification: {
- label: 'Notify when new email is received',
- options: [
- 'off',
- 'desktop notification',
- 'desktop & sound notification',
- ],
- },
- unreadMessagesNotification: {
- label: 'Remind about unread emails via desktop notification',
- options: [
- 'off',
- 'every 5 min',
- 'every 15 min',
- 'every 30 min',
- ],
- },
- unreadMessagesSound: {
- label: 'Remind about unread emails via sound notification',
- description: 'Plays according to the reminder interval',
- },
- notAuthNotification: {
- label: 'Notify if you\'re not logged in',
- description: 'Every 5 min',
- options: [
- 'off',
- 'desktop notification',
- 'desktop & sound notification',
- ],
- },
- setShortcuts: {
- label: 'Shortcuts',
- linkText: 'edit',
- },
- preferredDomain: {
- label: 'Preferred domain',
- description: 'Will be used for authorization, links etc',
- options: [
- 'ru',
- 'ua',
- 'by',
- 'kz',
- 'com',
- 'com.tr',
- ],
- },
+ emptyList: 'You have no unread emails',
+ loadingError: 'Cannot load emails... Please try again later',
+ unavailable: {
+ title: 'Something went wrong...',
+ subTitle: 'There are a few possible reasons why the extension may not work properly:',
+ notAuth: {
+ label: 'You are not authorized.',
+ checkAuth: 'Check that you\'re authorized on Yandex (exactly .$1 domain). Also you can choose another domain in the extension settings.',
+ relogin: 'Try to logout and login again.',
+ },
+ noConnection: {
+ label: 'There is no connection to the Internet.',
+ },
+ nothingHelped: 'If nothing above helped, you can try to reload the extension.',
+ reloadBtn: 'Reload extension',
},
+ donation: {
+ text: 'Want more settings, new features and just stable work of the extension?',
+ donateBtn: 'Support the project!',
+ },
+ months: [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ],
+ },
+ settings: {
+ newMessageNotification: {
+ label: 'Notify when new email is received',
+ options: [
+ 'off',
+ 'desktop notification',
+ 'desktop & sound notification',
+ ],
+ },
+ unreadMessagesNotification: {
+ label: 'Remind about unread emails via desktop notification',
+ options: [
+ 'off',
+ 'every 5 min',
+ 'every 15 min',
+ 'every 30 min',
+ ],
+ },
+ unreadMessagesSound: {
+ label: 'Remind about unread emails via sound notification',
+ description: 'Plays according to the reminder interval',
+ },
+ notAuthNotification: {
+ label: 'Notify if you\'re not logged in',
+ description: 'Every 5 min',
+ options: [
+ 'off',
+ 'desktop notification',
+ 'desktop & sound notification',
+ ],
+ },
+ setShortcuts: {
+ label: 'Shortcuts',
+ linkText: 'edit',
+ },
+ preferredDomain: {
+ label: 'Preferred domain',
+ description: 'Will be used for authorization, links etc',
+ options: [
+ 'ru',
+ 'ua',
+ 'by',
+ 'kz',
+ 'com',
+ 'com.tr',
+ ],
+ },
+ },
};
diff --git a/src/locales/ru.js b/src/locales/ru.js
index e05ca96..d8ae59d 100644
--- a/src/locales/ru.js
+++ b/src/locales/ru.js
@@ -1,108 +1,108 @@
module.exports = {
- ext: {
- name: 'Клиент для Яндекс.Почта',
- description: 'Удобный клиент для Яндекс.Почта',
+ ext: {
+ name: 'Клиент для Яндекс.Почта',
+ description: 'Удобный клиент для Яндекс.Почта',
+ },
+ notification: {
+ unread: {
+ title: 'У вас есть непрочитанные письма',
+ message: 'Непрочитанных писем: $1',
},
- notification: {
- unread: {
- title: 'У вас есть непрочитанные письма',
- message: 'Непрочитанных писем: $1',
- },
- notAuth: {
- title: 'Вы не авторизованы',
- message: 'Пожалуйста, войдите в свой Яндекс аккаунт',
- },
+ notAuth: {
+ title: 'Вы не авторизованы',
+ message: 'Пожалуйста, войдите в свой Яндекс аккаунт',
},
- popup: {
- title: 'Яндекс.Почта',
- compose: 'Написать',
- actions: {
- open: 'Открыть',
- read: 'Прочитано',
- spam: 'Спам',
- remove: 'Удалить',
- },
- emptyList: 'У вас нет непрочитанных писем',
- loadingError: 'Не удалось загрузить письма... Попробуйте позже',
- unavailable: {
- title: 'Что-то пошло не так...',
- subTitle: 'Возможны следующие причины почему расширение может работать не корректно:',
- notAuth: {
- label: 'Вы не авторизованы.',
- checkAuth: 'Проверьте, что вы авторизованы в Яндекс (именно .$1 домен). Также вы можете выбрать другой домен в настройках расширения.',
- relogin: 'Попробуйте разлогиниться и залогиниться снова.',
- },
- noConnection: {
- label: 'Отсутствует соединение с интернетом.',
- },
- nothingHelped: 'Если ничего из вышеперечисленного не помогло, попробуйте перезагрузить расширение.',
- reloadBtn: 'Перезагрузить расширение',
- },
- donation: {
- text: 'Хотите новых настроек, функций и просто стабильной работы расширения?',
- donateBtn: 'Поддержите проект!',
- },
- months: [
- 'Января',
- 'Февраля',
- 'Марта',
- 'Апреля',
- 'Мая',
- 'Июня',
- 'Июля',
- 'Августа',
- 'Сентября',
- 'Октября',
- 'Ноября',
- 'Декабря',
- ],
+ },
+ popup: {
+ title: 'Яндекс.Почта',
+ compose: 'Написать',
+ actions: {
+ open: 'Открыть',
+ read: 'Прочитано',
+ spam: 'Спам',
+ remove: 'Удалить',
},
- settings: {
- newMessageNotification: {
- label: 'Показывать уведомление при получении нового письма',
- options: [
- 'отключено',
- 'только текстовое',
- 'текстовое и звуковое',
- ],
- },
- unreadMessagesNotification: {
- label: 'Показывать текстовое уведомление при наличие непрочитанных писем',
- options: [
- 'отключено',
- 'каждые 5 мин',
- 'каждые 15 мин',
- 'каждые 30 мин',
- ],
- },
- unreadMessagesSound: {
- label: 'Проигрывать звуковое уведомление при наличие непрочитанных писем',
- description: 'В соответствие с интервалом выбраным выше',
- },
- notAuthNotification: {
- label: 'Показывать уведомления если вы не авторизованы',
- description: 'Каждые 5 мин',
- options: [
- 'отключено',
- 'только текстовое',
- 'текстовое и звуковое',
- ],
- },
- setShortcuts: {
- label: 'Быстрые клавиши',
- linkText: 'редактировать',
- },
- preferredDomain: {
- label: 'Предпочитаемый домен',
- description: 'Будет использован для авторизиции, ссылок и т.д.',
- options: [
- 'ru',
- 'ua',
- 'by',
- 'kz',
- 'com',
- 'com.tr',
- ],
- },
+ emptyList: 'У вас нет непрочитанных писем',
+ loadingError: 'Не удалось загрузить письма... Попробуйте позже',
+ unavailable: {
+ title: 'Что-то пошло не так...',
+ subTitle: 'Возможны следующие причины почему расширение может работать не корректно:',
+ notAuth: {
+ label: 'Вы не авторизованы.',
+ checkAuth: 'Проверьте, что вы авторизованы в Яндекс (именно .$1 домен). Также вы можете выбрать другой домен в настройках расширения.',
+ relogin: 'Попробуйте разлогиниться и залогиниться снова.',
+ },
+ noConnection: {
+ label: 'Отсутствует соединение с интернетом.',
+ },
+ nothingHelped: 'Если ничего из вышеперечисленного не помогло, попробуйте перезагрузить расширение.',
+ reloadBtn: 'Перезагрузить расширение',
},
+ donation: {
+ text: 'Хотите новых настроек, функций и просто стабильной работы расширения?',
+ donateBtn: 'Поддержите проект!',
+ },
+ months: [
+ 'Января',
+ 'Февраля',
+ 'Марта',
+ 'Апреля',
+ 'Мая',
+ 'Июня',
+ 'Июля',
+ 'Августа',
+ 'Сентября',
+ 'Октября',
+ 'Ноября',
+ 'Декабря',
+ ],
+ },
+ settings: {
+ newMessageNotification: {
+ label: 'Показывать уведомление при получении нового письма',
+ options: [
+ 'отключено',
+ 'только текстовое',
+ 'текстовое и звуковое',
+ ],
+ },
+ unreadMessagesNotification: {
+ label: 'Показывать текстовое уведомление при наличие непрочитанных писем',
+ options: [
+ 'отключено',
+ 'каждые 5 мин',
+ 'каждые 15 мин',
+ 'каждые 30 мин',
+ ],
+ },
+ unreadMessagesSound: {
+ label: 'Проигрывать звуковое уведомление при наличие непрочитанных писем',
+ description: 'В соответствие с интервалом выбраным выше',
+ },
+ notAuthNotification: {
+ label: 'Показывать уведомления если вы не авторизованы',
+ description: 'Каждые 5 мин',
+ options: [
+ 'отключено',
+ 'только текстовое',
+ 'текстовое и звуковое',
+ ],
+ },
+ setShortcuts: {
+ label: 'Быстрые клавиши',
+ linkText: 'редактировать',
+ },
+ preferredDomain: {
+ label: 'Предпочитаемый домен',
+ description: 'Будет использован для авторизиции, ссылок и т.д.',
+ options: [
+ 'ru',
+ 'ua',
+ 'by',
+ 'kz',
+ 'com',
+ 'com.tr',
+ ],
+ },
+ },
};
diff --git a/src/manifest/chrome.js b/src/manifest/chrome.js
index af03244..afc6a77 100644
--- a/src/manifest/chrome.js
+++ b/src/manifest/chrome.js
@@ -1,9 +1,9 @@
module.exports = {
- minimum_chrome_version: '55',
- options_ui: {
- page: 'pages/settings.html',
- chrome_style: true,
- },
- incognito: 'spanning',
- content_security_policy: "script-src 'self' 'unsafe-eval' http://localhost:8080; object-src 'self'",
+ minimum_chrome_version: '55',
+ options_ui: {
+ page: 'pages/settings.html',
+ chrome_style: true,
+ },
+ incognito: 'spanning',
+ content_security_policy: "script-src 'self' 'unsafe-eval' http://localhost:8080; object-src 'self'",
};
diff --git a/src/manifest/firefox.js b/src/manifest/firefox.js
index 158a1ce..ec34b19 100644
--- a/src/manifest/firefox.js
+++ b/src/manifest/firefox.js
@@ -1,11 +1,11 @@
module.exports = {
- applications: {
- gecko: {
- id: 'jid0-vsitoD18LSTi4VQGGLCVmIoJSsU@jetpack',
- strict_min_version: '53.0',
- },
- },
- options_ui: {
- page: 'pages/settings.html',
+ applications: {
+ gecko: {
+ id: 'jid0-vsitoD18LSTi4VQGGLCVmIoJSsU@jetpack',
+ strict_min_version: '53.0',
},
+ },
+ options_ui: {
+ page: 'pages/settings.html',
+ },
};
diff --git a/src/manifest/index.js b/src/manifest/index.js
index 1a503fe..9a3aa28 100644
--- a/src/manifest/index.js
+++ b/src/manifest/index.js
@@ -1,4 +1,6 @@
const baseManifest = require('./base.json');
-// eslint-disable-next-line global-require, import/no-dynamic-require
-module.exports = (target) => Object.assign({}, baseManifest, require(`./${target}`));
+module.exports = (target) => ({
+ ...baseManifest,
+ ...require(`./${target}`), // eslint-disable-line global-require, import/no-dynamic-require
+});
diff --git a/src/pages/background/index.js b/src/pages/background/index.js
index c926a30..df53cea 100644
--- a/src/pages/background/index.js
+++ b/src/pages/background/index.js
@@ -9,26 +9,26 @@ import initNotification from './modules/notification';
import initButtonState from './modules/popup-button';
async function loadStorageData() {
- const data = await storage.getAll();
+ const data = await storage.getAll();
- store.dispatch({
- type: LOAD_STORAGE_DATA,
- data,
- });
+ store.dispatch({
+ type: LOAD_STORAGE_DATA,
+ data,
+ });
}
function initModules() {
- initWS();
- initCookieListener();
- initNotification();
- initButtonState();
+ initWS();
+ initCookieListener();
+ initNotification();
+ initButtonState();
}
(async () => {
- // we should prepare the store before starting any service
- await loadStorageData();
+ // we should prepare the store before starting any service
+ await loadStorageData();
- initModules();
+ initModules();
- store.dispatch(login());
+ store.dispatch(login());
})();
diff --git a/src/pages/background/modules/cookie.js b/src/pages/background/modules/cookie.js
index d9cf111..09fc98c 100644
--- a/src/pages/background/modules/cookie.js
+++ b/src/pages/background/modules/cookie.js
@@ -5,53 +5,53 @@ import {config} from '../utils/cookie';
import resolveUrl from '../utils/url-resolver';
const handleCookieChange = debounce(({
- prevLogin,
- nextLogin,
- removed,
+ prevLogin,
+ nextLogin,
+ removed,
}) => {
- // user logged out from all accounts
- if (removed) {
- console.log('LOGOUT', prevLogin, nextLogin, removed);
- store.dispatch(logout());
- return;
- }
+ // user logged out from all accounts
+ if (removed) {
+ console.log('LOGOUT', prevLogin, nextLogin, removed);
+ store.dispatch(logout());
+ return;
+ }
- // user just logged in for the first time
- if (!prevLogin && nextLogin) {
- console.log('LOGIN', prevLogin, nextLogin, removed);
- store.dispatch(login());
- return;
- }
+ // user just logged in for the first time
+ if (!prevLogin && nextLogin) {
+ console.log('LOGIN', prevLogin, nextLogin, removed);
+ store.dispatch(login());
+ return;
+ }
- // user changed the account
- if (prevLogin !== nextLogin) {
- console.log('ACCOUNT CHANGED', prevLogin, nextLogin, removed);
- store.dispatch(logout());
- store.dispatch(login());
- return;
- }
+ // user changed the account
+ if (prevLogin !== nextLogin) {
+ console.log('ACCOUNT CHANGED', prevLogin, nextLogin, removed);
+ store.dispatch(logout());
+ store.dispatch(login());
+ return;
+ }
- console.log('SOMETHING ELSE', prevLogin, nextLogin, removed);
+ console.log('SOMETHING ELSE', prevLogin, nextLogin, removed);
}, 300);
export default function initCookieListener() {
- chrome.cookies.onChanged.addListener(({cookie, removed}) => {
- const {
- domain,
- name,
- value,
- path,
- } = cookie;
+ chrome.cookies.onChanged.addListener(({cookie, removed}) => {
+ const {
+ domain,
+ name,
+ value,
+ path,
+ } = cookie;
- if (domain.includes(resolveUrl(config.domain)) &&
+ if (domain.includes(resolveUrl(config.domain)) &&
name === config.items.login &&
path === config.path
- ) {
- handleCookieChange({
- prevLogin: store.getState().user.email,
- nextLogin: value,
- removed,
- });
- }
- });
+ ) {
+ handleCookieChange({
+ prevLogin: store.getState().user.email,
+ nextLogin: value,
+ removed,
+ });
+ }
+ });
}
diff --git a/src/pages/background/modules/notification.js b/src/pages/background/modules/notification.js
index 1a83a95..53cfdeb 100644
--- a/src/pages/background/modules/notification.js
+++ b/src/pages/background/modules/notification.js
@@ -3,60 +3,58 @@ import store from '../redux/store';
import {showUnreadNotification, showNotAuthNotification} from '../redux/actions/notification';
const ALARMS = {
- UNREAD: 'UNREAD',
- NOT_AUTH: 'NOT_AUTH',
+ UNREAD: 'UNREAD',
+ NOT_AUTH: 'NOT_AUTH',
};
const {alarms} = chrome;
alarms.onAlarm.addListener(({name}) => {
- const {dispatch} = store;
+ const {dispatch} = store;
- if (name === ALARMS.UNREAD) {
- dispatch(showUnreadNotification());
- }
- else if (name === ALARMS.NOT_AUTH) {
- dispatch(showNotAuthNotification());
- }
+ if (name === ALARMS.UNREAD) {
+ dispatch(showUnreadNotification());
+ } else if (name === ALARMS.NOT_AUTH) {
+ dispatch(showNotAuthNotification());
+ }
});
function initUnreadNotification({user, settings: {unreadMessagesNotification}}) {
- // on "unreadMessagesNotification" change + user is logged out
- if (!user.authorized) {
- return;
- }
+ // on "unreadMessagesNotification" change + user is logged out
+ if (!user.authorized) {
+ return;
+ }
- alarms.clear(ALARMS.UNREAD); // on "unreadMessagesNotification" change + user is logged in
- alarms.clear(ALARMS.NOT_AUTH); // on login
+ alarms.clear(ALARMS.UNREAD); // on "unreadMessagesNotification" change + user is logged in
+ alarms.clear(ALARMS.NOT_AUTH); // on login
- if (unreadMessagesNotification) {
- alarms.create(ALARMS.UNREAD, {periodInMinutes: unreadMessagesNotification});
- }
+ if (unreadMessagesNotification) {
+ alarms.create(ALARMS.UNREAD, {periodInMinutes: unreadMessagesNotification});
+ }
}
function initNotAuthNotification({user, settings}) {
- // on "notAuthNotification" change + user is logged in
- if (user.authorized) {
- return;
- }
+ // on "notAuthNotification" change + user is logged in
+ if (user.authorized) {
+ return;
+ }
- alarms.clear(ALARMS.NOT_AUTH); // on "notAuthNotification" change + user is logged out
- alarms.clear(ALARMS.UNREAD); // on logout
+ alarms.clear(ALARMS.NOT_AUTH); // on "notAuthNotification" change + user is logged out
+ alarms.clear(ALARMS.UNREAD); // on logout
- if (settings.notAuthNotification) {
- alarms.create(ALARMS.NOT_AUTH, {periodInMinutes: 5});
- }
+ if (settings.notAuthNotification) {
+ alarms.create(ALARMS.NOT_AUTH, {periodInMinutes: 5});
+ }
}
export default function () {
- subscribe('user.authorized', (state) => {
- if (state.user.authorized) {
- initUnreadNotification(state);
- }
- else {
- initNotAuthNotification(state);
- }
- });
-
- subscribe('settings.unreadMessagesNotification', initUnreadNotification);
- subscribe('settings.notAuthNotification', initNotAuthNotification);
+ subscribe('user.authorized', (state) => {
+ if (state.user.authorized) {
+ initUnreadNotification(state);
+ } else {
+ initNotAuthNotification(state);
+ }
+ });
+
+ subscribe('settings.unreadMessagesNotification', initUnreadNotification);
+ subscribe('settings.notAuthNotification', initNotAuthNotification);
}
diff --git a/src/pages/background/modules/popup-button.js b/src/pages/background/modules/popup-button.js
index 8ecfcb2..9bb1520 100644
--- a/src/pages/background/modules/popup-button.js
+++ b/src/pages/background/modules/popup-button.js
@@ -3,23 +3,22 @@ import {subscribe} from 'redux-subscriber';
const popup = chrome.browserAction;
function setState({user, messages}) {
- const enabled = user.authorized;
- const badge = (messages.unreadCount || '').toString();
+ const enabled = user.authorized;
+ const badge = (messages.unreadCount || '').toString();
- // enable or disable popup button (onClick works only if no popup set)
- if (enabled) {
- popup.setIcon({path: '/assets/icon.png'});
- }
- else {
- popup.setIcon({path: '/assets/icon-disabled.png'});
- }
+ // enable or disable popup button (onClick works only if no popup set)
+ if (enabled) {
+ popup.setIcon({path: '/assets/icon.png'});
+ } else {
+ popup.setIcon({path: '/assets/icon-disabled.png'});
+ }
- // set badge
- popup.setBadgeText({text: badge});
- popup.setBadgeBackgroundColor({color: '#3d5afe'});
+ // set badge
+ popup.setBadgeText({text: badge});
+ popup.setBadgeBackgroundColor({color: '#3d5afe'});
}
export default function () {
- subscribe('user.authorized', setState);
- subscribe('messages.unreadCount', setState);
+ subscribe('user.authorized', setState);
+ subscribe('messages.unreadCount', setState);
}
diff --git a/src/pages/background/modules/storage.js b/src/pages/background/modules/storage.js
index 5d7a1a2..01c5509 100644
--- a/src/pages/background/modules/storage.js
+++ b/src/pages/background/modules/storage.js
@@ -1,32 +1,31 @@
const chromeStore = chrome.storage.sync;
export default {
- getAll() {
- return new Promise(resolve => {
- chromeStore.get(null, data => {
- const parsedData = Object.keys(data).reduce((obj, key) => {
- try {
- // eslint-disable-next-line no-param-reassign
- obj[key] = JSON.parse(data[key]);
- }
- catch (err) {
- // swallow
- }
+ getAll() {
+ return new Promise(resolve => {
+ chromeStore.get(null, data => {
+ const parsedData = Object.keys(data).reduce((obj, key) => {
+ try {
+ // eslint-disable-next-line no-param-reassign
+ obj[key] = JSON.parse(data[key]);
+ } catch (err) {
+ // swallow
+ }
- return obj;
- }, {});
+ return obj;
+ }, {});
- resolve(parsedData);
- });
- });
- },
- set(name, value) {
- chromeStore.set({[name]: JSON.stringify(value)});
- },
- remove(name) {
- chromeStore.remove(name);
- },
- clear() {
- chromeStore.clear();
- },
+ resolve(parsedData);
+ });
+ });
+ },
+ set(name, value) {
+ chromeStore.set({[name]: JSON.stringify(value)});
+ },
+ remove(name) {
+ chromeStore.remove(name);
+ },
+ clear() {
+ chromeStore.clear();
+ },
};
diff --git a/src/pages/background/modules/websocket/client.js b/src/pages/background/modules/websocket/client.js
index c4cf127..53a95f6 100644
--- a/src/pages/background/modules/websocket/client.js
+++ b/src/pages/background/modules/websocket/client.js
@@ -2,63 +2,63 @@ import qs from 'query-string';
import sessionId from '../../utils/session-generator';
import resolveUrl from '../../utils/url-resolver';
import {
- RECONNECT,
- MESSAGE,
+ RECONNECT,
+ MESSAGE,
} from './constants';
let ws, emitEvent;
function onClose(err) {
- console.error('[SOCKET]: Socket is closed', err); // eslint-disable-line no-console
- emitEvent(RECONNECT, err);
+ console.error('[SOCKET]: Socket is closed', err); // eslint-disable-line no-console
+ emitEvent(RECONNECT, err);
}
function onError(...args) {
- console.error('[SOCKET]: An error has occurred in socket', args); // eslint-disable-line no-console
- emitEvent(RECONNECT);
+ console.error('[SOCKET]: An error has occurred in socket', args); // eslint-disable-line no-console
+ emitEvent(RECONNECT);
}
function onMessage({data}) {
- emitEvent(MESSAGE, JSON.parse(data));
+ emitEvent(MESSAGE, JSON.parse(data));
}
function connect({
- uid,
- token,
+ uid,
+ token,
}) {
- const queryParams = qs.stringify({
- client: 'bar',
- service: 'mail',
- session: sessionId,
- oauth_token: token,
- uid,
- });
+ const queryParams = qs.stringify({
+ client: 'bar',
+ service: 'mail',
+ session: sessionId,
+ oauth_token: token,
+ uid,
+ });
// eslint-disable-next-line no-use-before-define
// disconnect();
- ws = new WebSocket(resolveUrl(`wss://push.yandex.{domain}/v1/subscribe?${queryParams}`));
+ ws = new WebSocket(resolveUrl(`wss://push.yandex.{domain}/v1/subscribe?${queryParams}`));
- ws.addEventListener('close', onClose, false);
- ws.addEventListener('message', onMessage, false);
- ws.addEventListener('error', onError, false);
+ ws.addEventListener('close', onClose, false);
+ ws.addEventListener('message', onMessage, false);
+ ws.addEventListener('error', onError, false);
}
function disconnect() {
- if (ws) {
- ws.close();
+ if (ws) {
+ ws.close();
- ws.removeEventListener('close', onClose, false);
- ws.removeEventListener('message', onMessage, false);
- ws.removeEventListener('error', onError, false);
- }
+ ws.removeEventListener('close', onClose, false);
+ ws.removeEventListener('message', onMessage, false);
+ ws.removeEventListener('error', onError, false);
+ }
}
export default function (ev) {
- emitEvent = ev;
+ emitEvent = ev;
- return {
- connect,
- disconnect,
- };
+ return {
+ connect,
+ disconnect,
+ };
}
diff --git a/src/pages/background/modules/websocket/index.js b/src/pages/background/modules/websocket/index.js
index 953b4a8..f8b4b30 100644
--- a/src/pages/background/modules/websocket/index.js
+++ b/src/pages/background/modules/websocket/index.js
@@ -5,147 +5,144 @@ import {login, logout} from '../../redux/actions/user';
import {loadMessagesCount} from '../../redux/actions/messages';
import {showNewNotification} from '../../redux/actions/notification';
import {
- RECONNECT,
- MESSAGE,
+ RECONNECT,
+ MESSAGE,
} from './constants';
import initWSClient from './client';
const {alarms} = chrome;
const config = {
- connect: {
- name: 'connect',
- time: 1, // try to connect if failed before
- },
- reconnect: {
- name: 'reconnect',
- time: 1.9, // reconnect to websocket; we use double value to prevent receiving the 3rd ping before reconnect
- },
+ connect: {
+ name: 'connect',
+ time: 1, // try to connect if failed before
+ },
+ reconnect: {
+ name: 'reconnect',
+ time: 1.9, // reconnect to websocket; we use double value to prevent receiving the 3rd ping before reconnect
+ },
};
let wsClient;
async function connect() {
- const {dispatch, getState} = store;
- const {
- authorized,
- token,
- uid,
- } = getState().user;
-
- alarms.clear(config.reconnect.name);
-
- try {
- alarms.create(config.reconnect.name, {delayInMinutes: config.reconnect.time});
-
- wsClient.connect({
- uid,
- token,
- });
-
- // we don't need to dispatch login action if user is already authorized
- // e.g. after calling "connect" first time the subscriber below will call "reconnect"
- if (!authorized) {
- dispatch(login());
- }
- }
- catch (err) {
- dispatch(logout());
+ const {dispatch, getState} = store;
+ const {
+ authorized,
+ token,
+ uid,
+ } = getState().user;
+
+ alarms.clear(config.reconnect.name);
- alarms.create(config.connect.name, {delayInMinutes: config.connect.time});
+ try {
+ alarms.create(config.reconnect.name, {delayInMinutes: config.reconnect.time});
- // throw unhandled exception for raven
- throw err;
+ wsClient.connect({
+ uid,
+ token,
+ });
+
+ // we don't need to dispatch login action if user is already authorized
+ // e.g. after calling "connect" first time the subscriber below will call "reconnect"
+ if (!authorized) {
+ dispatch(login());
}
+ } catch (err) {
+ dispatch(logout());
+
+ alarms.create(config.connect.name, {delayInMinutes: config.connect.time});
+
+ // throw unhandled exception for raven
+ throw err;
+ }
}
const reconnect = debounce((err) => {
- wsClient.disconnect();
-
- if (err && err.code) {
- window.Raven.captureException(err, {
- extra: {
- code: err.code,
- reason: err.reason,
- },
- });
- store.dispatch(logout());
- store.dispatch(login());
- // TODO: if the error wouldn't disappear enable reloading
- // reloadApp();
-
- return;
- }
+ wsClient.disconnect();
+
+ if (err && err.code) {
+ window.Raven.captureException(err, {
+ extra: {
+ code: err.code,
+ reason: err.reason,
+ },
+ });
+ store.dispatch(logout());
+ store.dispatch(login());
+ // TODO: if the error wouldn't disappear enable reloading
+ // reloadApp();
- connect();
+ return;
+ }
+
+ connect();
}, 500);
function disconnect() {
- alarms.clear(config.reconnect.name);
- wsClient.disconnect();
+ alarms.clear(config.reconnect.name);
+ wsClient.disconnect();
}
function onMessage(data) {
- const {dispatch} = store;
- const {
- operation,
- message,
- } = data;
-
- if (operation !== 'insert' || message.hdr_status !== 'New') {
- dispatch(loadMessagesCount());
- return;
- }
-
- const {
- new_messages: unreadCount,
- mid: id,
- hdr_from: from,
- hdr_subject: subject,
- firstline,
- } = message;
-
- const nameMatch = from.match(/^"(.+)"/);
- const emailMatch = from.match(/<(.+)>$/);
-
- dispatch(loadMessagesCount(parseInt(unreadCount, 10)));
- dispatch(showNewNotification({
- id,
- from: nameMatch[1] || emailMatch[1],
- subject: subject !== 'No subject' ? subject : '',
- message: firstline,
- }));
+ const {dispatch} = store;
+ const {
+ operation,
+ message,
+ } = data;
+
+ if (operation !== 'insert' || message.hdr_status !== 'New') {
+ dispatch(loadMessagesCount());
+ return;
+ }
+
+ const {
+ new_messages: unreadCount,
+ mid: id,
+ hdr_from: from,
+ hdr_subject: subject,
+ firstline,
+ } = message;
+
+ const nameMatch = from.match(/^"(.+)"/);
+ const emailMatch = from.match(/<(.+)>$/);
+
+ dispatch(loadMessagesCount(parseInt(unreadCount, 10)));
+ dispatch(showNewNotification({
+ id,
+ from: nameMatch[1] || emailMatch[1],
+ subject: subject !== 'No subject' ? subject : '',
+ message: firstline,
+ }));
}
function emitEvent(eventType, data) {
- if (eventType === RECONNECT) {
- reconnect(data);
- }
- if (eventType === MESSAGE) {
- onMessage(data);
- }
+ if (eventType === RECONNECT) {
+ reconnect(data);
+ }
+ if (eventType === MESSAGE) {
+ onMessage(data);
+ }
}
function handleAlarm({name}) {
- if (name === config.connect.name) {
- connect();
- }
- else if (name === config.reconnect.name) {
- reconnect();
- }
+ if (name === config.connect.name) {
+ connect();
+ } else if (name === config.reconnect.name) {
+ reconnect();
+ }
}
export default function () {
- wsClient = initWSClient(emitEvent);
+ wsClient = initWSClient(emitEvent);
- alarms.onAlarm.addListener(handleAlarm);
+ alarms.onAlarm.addListener(handleAlarm);
- subscribe('user.authorized', ({user: {authorized}}) => {
- if (authorized) {
- reconnect();
- }
- else {
- disconnect();
- }
- });
+ subscribe('user.authorized', ({user: {authorized}}) => {
+ if (authorized) {
+ reconnect();
+ } else {
+ disconnect();
+ }
+ });
}
diff --git a/src/pages/background/redux/actions/messages.js b/src/pages/background/redux/actions/messages.js
index 77d6a95..286b4ab 100644
--- a/src/pages/background/redux/actions/messages.js
+++ b/src/pages/background/redux/actions/messages.js
@@ -1,69 +1,68 @@
import {
- LOAD_MESSAGES_COUNT,
- LOAD_MESSAGES,
- LOAD_MESSAGES_SUCCESS,
- LOAD_MESSAGES_ERROR,
- UPDATE_MESSAGE,
- INVALIDATE_MESSAGES,
+ LOAD_MESSAGES_COUNT,
+ LOAD_MESSAGES,
+ LOAD_MESSAGES_SUCCESS,
+ LOAD_MESSAGES_ERROR,
+ UPDATE_MESSAGE,
+ INVALIDATE_MESSAGES,
} from 'shared/redux-consts/messages';
import {
- getMessages,
- getMessagesCount,
- updateMessageStatus,
+ getMessages,
+ getMessagesCount,
+ updateMessageStatus,
} from '../../utils/api';
let popupIsOpen = false;
export function loadMessagesCount(data) {
- return async (dispatch) => {
- const unreadMessagesCount = data || await getMessagesCount();
+ return async (dispatch) => {
+ const unreadMessagesCount = data || await getMessagesCount();
- dispatch({
- type: LOAD_MESSAGES_COUNT,
- data: unreadMessagesCount,
- });
- };
+ dispatch({
+ type: LOAD_MESSAGES_COUNT,
+ data: unreadMessagesCount,
+ });
+ };
}
export function loadMessages() {
- return async (dispatch) => {
- popupIsOpen = true;
+ return async (dispatch) => {
+ popupIsOpen = true;
- dispatch({type: LOAD_MESSAGES});
+ dispatch({type: LOAD_MESSAGES});
- try {
- const data = await getMessages();
+ try {
+ const data = await getMessages();
- // to prevent rewriting "loading" value, we dispatch the action only if popup is open
- // "popupIsOpen" could be rewritten by "invalidateMessages" action
- if (popupIsOpen) {
- dispatch({
- type: LOAD_MESSAGES_SUCCESS,
- data,
- });
- }
- }
- catch (err) {
- dispatch({type: LOAD_MESSAGES_ERROR});
+ // to prevent rewriting "loading" value, we dispatch the action only if popup is open
+ // "popupIsOpen" could be rewritten by "invalidateMessages" action
+ if (popupIsOpen) {
+ dispatch({
+ type: LOAD_MESSAGES_SUCCESS,
+ data,
+ });
+ }
+ } catch (err) {
+ dispatch({type: LOAD_MESSAGES_ERROR});
- // throw unhandled exception for raven
- throw err;
- }
- };
+ // throw unhandled exception for raven
+ throw err;
+ }
+ };
}
export function updateMessage({data}) {
- updateMessageStatus(data);
+ updateMessageStatus(data);
- // kind of optimistic update :)
- return {
- type: UPDATE_MESSAGE,
- id: data.id,
- };
+ // kind of optimistic update :)
+ return {
+ type: UPDATE_MESSAGE,
+ id: data.id,
+ };
}
export function invalidateMessages() {
- popupIsOpen = false;
+ popupIsOpen = false;
- return {type: INVALIDATE_MESSAGES};
+ return {type: INVALIDATE_MESSAGES};
}
diff --git a/src/pages/background/redux/actions/notification.js b/src/pages/background/redux/actions/notification.js
index 6bb0e12..aa8bc13 100644
--- a/src/pages/background/redux/actions/notification.js
+++ b/src/pages/background/redux/actions/notification.js
@@ -2,9 +2,9 @@ import i18n from 'shared/utils/i18n';
import {openUrl} from '../../utils/tab';
const CONSTANTS = {
- SHOW_NEW_NOTIFICATION: 'SHOW_NEW_NOTIFICATION',
- SHOW_UNREAD_NOTIFICATION: 'SHOW_UNREAD_NOTIFICATION',
- SHOW_NOT_AUTH_NOTIFICATION: 'SHOW_NOT_AUTH_NOTIFICATION',
+ SHOW_NEW_NOTIFICATION: 'SHOW_NEW_NOTIFICATION',
+ SHOW_UNREAD_NOTIFICATION: 'SHOW_UNREAD_NOTIFICATION',
+ SHOW_NOT_AUTH_NOTIFICATION: 'SHOW_NOT_AUTH_NOTIFICATION',
};
const sevenSec = 7 * 1000;
@@ -13,93 +13,93 @@ const audio = new Audio('../assets/notification.wav');
const notifications = {};
notification.onClicked.addListener(id => {
- notifications[id]();
- delete notifications[id];
+ notifications[id]();
+ delete notifications[id];
});
function showNotification(data) {
- notification.create({
- type: 'basic',
- iconUrl: '../assets/icon.png',
- title: data.title,
- message: data.subTitle,
- contextMessage: data.message,
- }, (id) => {
- notifications[id] = data.onClick;
-
- setTimeout(() => {
- if (id) {
- notification.clear(id);
- }
- }, sevenSec);
- });
+ notification.create({
+ type: 'basic',
+ iconUrl: '../assets/icon.png',
+ title: data.title,
+ message: data.subTitle,
+ contextMessage: data.message,
+ }, (id) => {
+ notifications[id] = data.onClick;
+
+ setTimeout(() => {
+ if (id) {
+ notification.clear(id);
+ }
+ }, sevenSec);
+ });
}
function playSound() {
- audio.play();
+ audio.play();
}
export function showNewNotification(data) {
- return (dispatch, getState) => {
- const {newMessageNotification} = getState().settings;
-
- if (newMessageNotification) {
- showNotification({
- title: data.from,
- subTitle: data.subject,
- message: data.message,
- onClick() {
- openUrl(`#message/${data.id}`);
- },
- });
-
- if (newMessageNotification === 2) {
- playSound();
- }
-
- dispatch({type: CONSTANTS.SHOW_NEW_NOTIFICATION});
- }
- };
+ return (dispatch, getState) => {
+ const {newMessageNotification} = getState().settings;
+
+ if (newMessageNotification) {
+ showNotification({
+ title: data.from,
+ subTitle: data.subject,
+ message: data.message,
+ onClick() {
+ openUrl(`#message/${data.id}`);
+ },
+ });
+
+ if (newMessageNotification === 2) {
+ playSound();
+ }
+
+ dispatch({type: CONSTANTS.SHOW_NEW_NOTIFICATION});
+ }
+ };
}
export function showUnreadNotification() {
- return (dispatch, getState) => {
- const {messages: {unreadCount}, settings} = getState();
+ return (dispatch, getState) => {
+ const {messages: {unreadCount}, settings} = getState();
- if (!unreadCount) {
- return;
- }
+ if (!unreadCount) {
+ return;
+ }
- showNotification({
- title: i18n.text('notification.unread.title'),
- subTitle: i18n.text('notification.unread.message', unreadCount),
- onClick: openUrl,
- });
+ showNotification({
+ title: i18n.text('notification.unread.title'),
+ subTitle: i18n.text('notification.unread.message', unreadCount),
+ onClick: openUrl,
+ });
- if (settings.unreadMessagesSound) {
- playSound();
- }
+ if (settings.unreadMessagesSound) {
+ playSound();
+ }
- dispatch({type: CONSTANTS.SHOW_UNREAD_NOTIFICATION});
- };
+ dispatch({type: CONSTANTS.SHOW_UNREAD_NOTIFICATION});
+ };
}
export function showNotAuthNotification() {
- return (dispatch, getState) => {
- const {notAuthNotification} = getState().settings;
-
- if (notAuthNotification) {
- showNotification({
- title: i18n.text('notification.notAuth.title'),
- subTitle: i18n.text('notification.notAuth.message'),
- onClick: openUrl,
- });
-
- if (notAuthNotification === 2) {
- playSound();
- }
-
- dispatch({type: CONSTANTS.SHOW_NOT_AUTH_NOTIFICATION});
- }
- };
+ return (dispatch, getState) => {
+ const {notAuthNotification} = getState().settings;
+
+ if (notAuthNotification) {
+ showNotification({
+ title: i18n.text('notification.notAuth.title'),
+ subTitle: i18n.text('notification.notAuth.message'),
+ onClick: openUrl,
+ });
+
+ if (notAuthNotification === 2) {
+ playSound();
+ }
+
+ dispatch({type: CONSTANTS.SHOW_NOT_AUTH_NOTIFICATION});
+ }
+ };
}
diff --git a/src/pages/background/redux/actions/popup.js b/src/pages/background/redux/actions/popup.js
index 15a91a4..560140a 100644
--- a/src/pages/background/redux/actions/popup.js
+++ b/src/pages/background/redux/actions/popup.js
@@ -1,33 +1,33 @@
import {
- OPEN_LINK,
- OPEN_SETTINGS,
- RELOAD_APP,
+ OPEN_LINK,
+ OPEN_SETTINGS,
+ RELOAD_APP,
} from 'shared/redux-consts/popup';
import * as tabUtils from '../../utils/tab';
import * as runtimeUtils from '../../utils/runtime';
export function openLink({url} = {}) {
- tabUtils.openUrl(url);
+ tabUtils.openUrl(url);
- return {type: OPEN_LINK};
+ return {type: OPEN_LINK};
}
export function openSettings() {
- tabUtils.openSettings();
+ tabUtils.openSettings();
- return {type: OPEN_SETTINGS};
+ return {type: OPEN_SETTINGS};
}
export function reloadApp() {
- window.Raven.captureMessage('[Event] reload-extension-click', {level: 'info'});
+ window.Raven.captureMessage('[Event] reload-extension-click', {level: 'info'});
- runtimeUtils.reloadApp();
+ runtimeUtils.reloadApp();
- return {type: RELOAD_APP};
+ return {type: RELOAD_APP};
}
export function openDonationLink() {
- window.Raven.captureMessage('[Event] donate-click', {level: 'info'});
+ window.Raven.captureMessage('[Event] donate-click', {level: 'info'});
- return openLink({url: 'https://www.paypal.me/yandexmailnotifier/5'});
+ return openLink({url: 'https://www.paypal.me/yandexmailnotifier/5'});
}
diff --git a/src/pages/background/redux/actions/settings.js b/src/pages/background/redux/actions/settings.js
index 5675bdc..9bdb416 100644
--- a/src/pages/background/redux/actions/settings.js
+++ b/src/pages/background/redux/actions/settings.js
@@ -4,22 +4,22 @@ import {reloadApp} from '../../utils/runtime';
// eslint-disable-next-line import/prefer-default-export
export function updateSettings(data) {
- return (dispatch, getState) => {
- const {settings} = getState();
- const nextSettings = {
- ...settings,
- ...data,
- };
+ return (dispatch, getState) => {
+ const {settings} = getState();
+ const nextSettings = {
+ ...settings,
+ ...data,
+ };
- storage.set('settings', nextSettings);
+ storage.set('settings', nextSettings);
- dispatch({
- type: UPDATE_SETTINGS,
- data: nextSettings,
- });
+ dispatch({
+ type: UPDATE_SETTINGS,
+ data: nextSettings,
+ });
- if (settings.preferredDomain !== nextSettings.preferredDomain) {
- reloadApp();
- }
- };
+ if (settings.preferredDomain !== nextSettings.preferredDomain) {
+ reloadApp();
+ }
+ };
}
diff --git a/src/pages/background/redux/actions/user.js b/src/pages/background/redux/actions/user.js
index ef30468..46ec856 100644
--- a/src/pages/background/redux/actions/user.js
+++ b/src/pages/background/redux/actions/user.js
@@ -3,23 +3,23 @@ import storage from '../../modules/storage';
import {loadUser} from '../../utils/api';
export function login() {
- return async (dispatch, getState) => {
- const {token} = getState().user;
- const data = await loadUser(token);
+ return async (dispatch, getState) => {
+ const {token} = getState().user;
+ const data = await loadUser(token);
- if (token !== data.token) {
- storage.set('user', {token: data.token});
- }
+ if (token !== data.token) {
+ storage.set('user', {token: data.token});
+ }
- dispatch({
- type: LOGIN,
- data,
- });
- };
+ dispatch({
+ type: LOGIN,
+ data,
+ });
+ };
}
export function logout() {
- storage.remove('user');
+ storage.remove('user');
- return {type: LOGOUT};
+ return {type: LOGOUT};
}
diff --git a/src/pages/background/redux/ext-store.js b/src/pages/background/redux/ext-store.js
index 51174dc..b3e14ad 100644
--- a/src/pages/background/redux/ext-store.js
+++ b/src/pages/background/redux/ext-store.js
@@ -1,41 +1,41 @@
import {createBackgroundStore} from 'redux-webext';
import {
- LOAD_MESSAGES,
- UPDATE_MESSAGE,
+ LOAD_MESSAGES,
+ UPDATE_MESSAGE,
} from 'shared/redux-consts/messages';
import {
- OPEN_LINK,
- OPEN_SETTINGS,
- RELOAD_APP,
- OPEN_DONATION_LINK,
+ OPEN_LINK,
+ OPEN_SETTINGS,
+ RELOAD_APP,
+ OPEN_DONATION_LINK,
} from 'shared/redux-consts/popup';
import {UPDATE_SETTINGS} from 'shared/redux-consts/settings';
import store from './store';
import {
- loadMessages,
- updateMessage,
- invalidateMessages,
+ loadMessages,
+ updateMessage,
+ invalidateMessages,
} from './actions/messages';
import {
- openLink,
- openSettings,
- reloadApp,
- openDonationLink,
+ openLink,
+ openSettings,
+ reloadApp,
+ openDonationLink,
} from './actions/popup';
import {updateSettings} from './actions/settings';
export default createBackgroundStore({
- store,
- actions: {
- [LOAD_MESSAGES]: loadMessages,
- [UPDATE_MESSAGE]: updateMessage,
- [OPEN_LINK]: openLink,
- [OPEN_SETTINGS]: openSettings,
- [RELOAD_APP]: reloadApp,
- [OPEN_DONATION_LINK]: openDonationLink,
- [UPDATE_SETTINGS]: updateSettings,
- },
- onDisconnect() {
- store.dispatch(invalidateMessages());
- },
+ store,
+ actions: {
+ [LOAD_MESSAGES]: loadMessages,
+ [UPDATE_MESSAGE]: updateMessage,
+ [OPEN_LINK]: openLink,
+ [OPEN_SETTINGS]: openSettings,
+ [RELOAD_APP]: reloadApp,
+ [OPEN_DONATION_LINK]: openDonationLink,
+ [UPDATE_SETTINGS]: updateSettings,
+ },
+ onDisconnect() {
+ store.dispatch(invalidateMessages());
+ },
});
diff --git a/src/pages/background/redux/middlewares/index.js b/src/pages/background/redux/middlewares/index.js
index 8d224fd..deb5eb2 100644
--- a/src/pages/background/redux/middlewares/index.js
+++ b/src/pages/background/redux/middlewares/index.js
@@ -4,12 +4,11 @@ const middlewares = [thunkMiddleware];
/* eslint-disable global-require */
if (__DEV__) {
- const {createLogger} = require('redux-logger');
- middlewares.push(createLogger({collapsed: true}));
-}
-else {
- const ravenMiddleware = require('./raven').default;
- middlewares.push(ravenMiddleware);
+ const {createLogger} = require('redux-logger');
+ middlewares.push(createLogger({collapsed: true}));
+} else {
+ const ravenMiddleware = require('./raven').default;
+ middlewares.push(ravenMiddleware);
}
/* eslint-enable global-require */
diff --git a/src/pages/background/redux/middlewares/raven.js b/src/pages/background/redux/middlewares/raven.js
index 6e42d4e..9514108 100644
--- a/src/pages/background/redux/middlewares/raven.js
+++ b/src/pages/background/redux/middlewares/raven.js
@@ -4,24 +4,24 @@ const {Raven} = window;
let state, lastAction;
window.onunhandledrejection = (err) => {
- Raven.captureException(err.reason, {
- extra: {
- lastAction,
- state,
- },
- });
+ Raven.captureException(err.reason, {
+ extra: {
+ lastAction,
+ state,
+ },
+ });
};
// eslint-disable-next-line consistent-return
export default store => next => action => {
- // save this data for unhandled exceptions
- state = store.getState();
- lastAction = action;
+ // save this data for unhandled exceptions
+ state = store.getState();
+ lastAction = action;
- Raven.captureBreadcrumb({
- category: 'redux',
- message: JSON.stringify(action),
- });
+ Raven.captureBreadcrumb({
+ category: 'redux',
+ message: JSON.stringify(action),
+ });
- return next(action);
+ return next(action);
};
diff --git a/src/pages/background/redux/reducers/index.js b/src/pages/background/redux/reducers/index.js
index 4542eb5..b83d652 100644
--- a/src/pages/background/redux/reducers/index.js
+++ b/src/pages/background/redux/reducers/index.js
@@ -5,16 +5,16 @@ import messages from './messages';
import settings from './settings';
const appReducer = combineReducers({
- user,
- messages,
- settings,
+ user,
+ messages,
+ settings,
});
export default function (state, action) {
- // clear the whole state except settings on logout action
- const nextState = action.type === LOGOUT ?
- {settings: state.settings} :
- state;
+ // clear the whole state except settings on logout action
+ const nextState = action.type === LOGOUT ?
+ {settings: state.settings} :
+ state;
- return appReducer(nextState, action);
+ return appReducer(nextState, action);
}
diff --git a/src/pages/background/redux/reducers/messages.js b/src/pages/background/redux/reducers/messages.js
index b50e331..e6f4aca 100644
--- a/src/pages/background/redux/reducers/messages.js
+++ b/src/pages/background/redux/reducers/messages.js
@@ -1,58 +1,58 @@
import {
- LOAD_MESSAGES_COUNT,
- LOAD_MESSAGES,
- LOAD_MESSAGES_SUCCESS,
- LOAD_MESSAGES_ERROR,
- UPDATE_MESSAGE,
- INVALIDATE_MESSAGES,
+ LOAD_MESSAGES_COUNT,
+ LOAD_MESSAGES,
+ LOAD_MESSAGES_SUCCESS,
+ LOAD_MESSAGES_ERROR,
+ UPDATE_MESSAGE,
+ INVALIDATE_MESSAGES,
} from 'shared/redux-consts/messages';
const initialState = {
- unreadCount: 0,
- items: [],
- loading: true,
- error: false,
+ unreadCount: 0,
+ items: [],
+ loading: true,
+ error: false,
};
// Another one solution for loading is showing cached messages with
// "New messages are loading..." and then "Show new msgs" button (smth like Inbox by google)
export default function (state = initialState, action) {
- switch (action.type) {
- case LOAD_MESSAGES_COUNT:
- return {
- ...state,
- unreadCount: action.data,
- };
- case LOAD_MESSAGES:
- case INVALIDATE_MESSAGES:
- return {
- ...state,
- loading: true,
- error: false,
- };
- case LOAD_MESSAGES_SUCCESS:
- return {
- ...state,
- items: action.data,
- loading: false,
- error: false,
- };
- case LOAD_MESSAGES_ERROR:
- return {
- ...state,
- loading: false,
- error: true,
- };
- case UPDATE_MESSAGE:
- // don't update unreadCount manually because it'll lead to race condition
- // e.g. we removed 5 messages at once but server handled only 3, we'll show user 10 - 5
- // but when we receive server response it'll be 10 - 3
- return {
- ...state,
- items: state.items.filter(({id}) => id !== action.id),
- };
- default:
- return state;
- }
+ switch (action.type) {
+ case LOAD_MESSAGES_COUNT:
+ return {
+ ...state,
+ unreadCount: action.data,
+ };
+ case LOAD_MESSAGES:
+ case INVALIDATE_MESSAGES:
+ return {
+ ...state,
+ loading: true,
+ error: false,
+ };
+ case LOAD_MESSAGES_SUCCESS:
+ return {
+ ...state,
+ items: action.data,
+ loading: false,
+ error: false,
+ };
+ case LOAD_MESSAGES_ERROR:
+ return {
+ ...state,
+ loading: false,
+ error: true,
+ };
+ case UPDATE_MESSAGE:
+ // don't update unreadCount manually because it'll lead to race condition
+ // e.g. we removed 5 messages at once but server handled only 3, we'll show user 10 - 5
+ // but when we receive server response it'll be 10 - 3
+ return {
+ ...state,
+ items: state.items.filter(({id}) => id !== action.id),
+ };
+ default:
+ return state;
+ }
}
diff --git a/src/pages/background/redux/reducers/settings.js b/src/pages/background/redux/reducers/settings.js
index 7e8ef46..39bc25f 100644
--- a/src/pages/background/redux/reducers/settings.js
+++ b/src/pages/background/redux/reducers/settings.js
@@ -3,26 +3,26 @@ import {LOAD_STORAGE_DATA} from 'shared/redux-consts/meta';
import {UPDATE_SETTINGS} from 'shared/redux-consts/settings';
const initialState = {
- newMessageNotification: 1, // only desktop notification
- unreadMessagesNotification: 5, // 5 min
- unreadMessagesSound: false,
- notAuthNotification: 1, // only desktop notification
- preferredDomain: appConfig.supportedDomains[0],
+ newMessageNotification: 1, // only desktop notification
+ unreadMessagesNotification: 5, // 5 min
+ unreadMessagesSound: false,
+ notAuthNotification: 1, // only desktop notification
+ preferredDomain: appConfig.supportedDomains[0],
};
export default function (state = initialState, action) {
- switch (action.type) {
- case LOAD_STORAGE_DATA:
- return {
- ...state,
- ...action.data.settings,
- };
- case UPDATE_SETTINGS:
- return {
- ...state,
- ...action.data,
- };
- default:
- return state;
- }
+ switch (action.type) {
+ case LOAD_STORAGE_DATA:
+ return {
+ ...state,
+ ...action.data.settings,
+ };
+ case UPDATE_SETTINGS:
+ return {
+ ...state,
+ ...action.data,
+ };
+ default:
+ return state;
+ }
}
diff --git a/src/pages/background/redux/reducers/user.js b/src/pages/background/redux/reducers/user.js
index 88e1190..3c3457d 100644
--- a/src/pages/background/redux/reducers/user.js
+++ b/src/pages/background/redux/reducers/user.js
@@ -2,26 +2,26 @@ import {LOAD_STORAGE_DATA} from 'shared/redux-consts/meta';
import {LOGIN} from 'shared/redux-consts/user';
const initialState = {
- authorized: false,
- email: '',
- uid: null,
- token: null,
+ authorized: false,
+ email: '',
+ uid: null,
+ token: null,
};
export default function (state = initialState, action) {
- switch (action.type) {
- case LOAD_STORAGE_DATA:
- return {
- ...state,
- ...action.data.user,
- };
- case LOGIN:
- return {
- ...state,
- ...action.data,
- authorized: true,
- };
- default:
- return state;
- }
+ switch (action.type) {
+ case LOAD_STORAGE_DATA:
+ return {
+ ...state,
+ ...action.data.user,
+ };
+ case LOGIN:
+ return {
+ ...state,
+ ...action.data,
+ authorized: true,
+ };
+ default:
+ return state;
+ }
}
diff --git a/src/pages/background/redux/store.js b/src/pages/background/redux/store.js
index d3d7b01..17c7d75 100644
--- a/src/pages/background/redux/store.js
+++ b/src/pages/background/redux/store.js
@@ -4,8 +4,8 @@ import middlewares from './middlewares';
import reducer from './reducers';
const store = createStore(
- reducer,
- applyMiddleware(...middlewares),
+ reducer,
+ applyMiddleware(...middlewares),
);
initSubscriber(store);
diff --git a/src/pages/background/utils/__tests__/parser/fixtures.js b/src/pages/background/utils/__tests__/parser/fixtures.js
index ce3dd41..f31e4d9 100644
--- a/src/pages/background/utils/__tests__/parser/fixtures.js
+++ b/src/pages/background/utils/__tests__/parser/fixtures.js
@@ -1,42 +1,42 @@
export const folders = [{
- id: '0123',
- symbol: 'spam',
+ id: '0123',
+ symbol: 'spam',
}, {
- id: '0234',
- symbol: 'inbox',
+ id: '0234',
+ symbol: 'inbox',
}, {
- id: '0345',
- symbol: 'draft',
+ id: '0345',
+ symbol: 'draft',
}];
export const messages = [{
- id: '123',
- subject: '',
- from: {
- name: '',
- email: 'sender@test.com 123',
- },
- firstline: 'test message 123',
- date: new Date(),
- fid: folders[0].id,
+ id: '123',
+ subject: '',
+ from: {
+ name: '',
+ email: 'sender@test.com 123',
+ },
+ firstline: 'test message 123',
+ date: new Date(),
+ fid: folders[0].id,
}, {
- id: '234',
- subject: 'No subject',
- from: {
- name: 'Sender 345',
- email: '',
- },
- firstline: 'test message 234',
- date: new Date(),
- fid: folders[1].id,
+ id: '234',
+ subject: 'No subject',
+ from: {
+ name: 'Sender 345',
+ email: '',
+ },
+ firstline: 'test message 234',
+ date: new Date(),
+ fid: folders[1].id,
}, {
- id: '345',
- subject: 'test subject 345',
- from: {
- name: 'Sender 345',
- email: 'sender@test.com 345',
- },
- firstline: 'test message 345',
- date: new Date(),
- fid: folders[2].id,
+ id: '345',
+ subject: 'test subject 345',
+ from: {
+ name: 'Sender 345',
+ email: 'sender@test.com 345',
+ },
+ firstline: 'test message 345',
+ date: new Date(),
+ fid: folders[2].id,
}];
diff --git a/src/pages/background/utils/__tests__/parser/index.js b/src/pages/background/utils/__tests__/parser/index.js
index 23b1685..17e6865 100644
--- a/src/pages/background/utils/__tests__/parser/index.js
+++ b/src/pages/background/utils/__tests__/parser/index.js
@@ -2,112 +2,111 @@ import {folders, messages} from './fixtures';
import parseXML from '../../parser';
function createElement(
- name,
- children = [],
- attrs = {},
+ name,
+ children = [],
+ attrs = {},
) {
- const element = document.createElement(name);
- children.forEach(child => element.appendChild(child));
+ const element = document.createElement(name);
+ children.forEach(child => element.appendChild(child));
- if (typeof attrs === 'object') {
- Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key]));
- }
- else {
- element.textContent = attrs;
- }
+ if (typeof attrs === 'object') {
+ Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key]));
+ } else {
+ element.textContent = attrs;
+ }
- return element;
+ return element;
}
const folderElements = folders.map(({id, symbol}) =>
- createElement('folder', [
- createElement('fid', [], id),
- createElement('symbol', [], symbol),
- ]),
+ createElement('folder', [
+ createElement('fid', [], id),
+ createElement('symbol', [], symbol),
+ ]),
);
const messageElements = messages.map(({
- id,
- subject,
- from,
- firstline,
- date,
- fid,
+ id,
+ subject,
+ from,
+ firstline,
+ date,
+ fid,
}) =>
- createElement('message', [
- createElement('from', [
- createElement('name', [], from.name),
- createElement('email', [], from.email),
- ]),
- createElement('subject', [
- createElement('text', [], subject),
- ]),
- createElement('firstline', [], firstline),
- ], {
- mid: id,
- recv_date: date,
- fid,
- }),
+ createElement('message', [
+ createElement('from', [
+ createElement('name', [], from.name),
+ createElement('email', [], from.email),
+ ]),
+ createElement('subject', [
+ createElement('text', [], subject),
+ ]),
+ createElement('firstline', [], firstline),
+ ], {
+ mid: id,
+ recv_date: date,
+ fid,
+ }),
);
describe('background/utils/parser', () => {
- describe('bad xml', () => {
- it('messages', () => {
- const xml = createElement('doc', [createElement('folder_list')]);
-
- expect(() => parseXML(xml)).toThrowError('Bad response format in messages xml');
- });
-
- it('folders', () => {
- const xml = createElement('doc', [createElement('mailbox_list')]);
-
- expect(() => parseXML(xml)).toThrowError('Bad response format in folders xml');
- });
-
- it('error', () => {
- const errorCode = 500;
- const xml = createElement('doc', [
- createElement('mailbox_list', [
- createElement('error', [], {code: errorCode}),
- ]),
- createElement('folder_list'),
- ]);
-
- expect(() => parseXML(xml)).toThrowError(`Messages xml has error field with code: ${errorCode}`);
- });
+ describe('bad xml', () => {
+ it('messages', () => {
+ const xml = createElement('doc', [createElement('folder_list')]);
+
+ expect(() => parseXML(xml)).toThrowError('Bad response format in messages xml');
});
- describe('valid xml', () => {
- it('no messages', () => {
- const expected = [];
- const xml = createElement('doc', [
- createElement('mailbox_list', expected),
- createElement('folder_list'),
- ]);
-
- expect(parseXML(xml)).toEqual(expected);
- });
-
- it('with folders filtering', () => {
- const xml = createElement('doc', [
- createElement('mailbox_list', [
- createElement('details'),
- ...messageElements,
- ]),
- createElement('folder_list', folderElements),
- ]);
-
- const items = parseXML(xml);
- const expected = messages[1];
- const result = items[0];
-
- expect(items.length).toBe(1);
- expect(result).toEqual({
- ...expected,
- date: expected.date.toString(),
- from: expected.from.name,
- subject: '',
- });
- });
+ it('folders', () => {
+ const xml = createElement('doc', [createElement('mailbox_list')]);
+
+ expect(() => parseXML(xml)).toThrowError('Bad response format in folders xml');
+ });
+
+ it('error', () => {
+ const errorCode = 500;
+ const xml = createElement('doc', [
+ createElement('mailbox_list', [
+ createElement('error', [], {code: errorCode}),
+ ]),
+ createElement('folder_list'),
+ ]);
+
+ expect(() => parseXML(xml)).toThrowError(`Messages xml has error field with code: ${errorCode}`);
+ });
+ });
+
+ describe('valid xml', () => {
+ it('no messages', () => {
+ const expected = [];
+ const xml = createElement('doc', [
+ createElement('mailbox_list', expected),
+ createElement('folder_list'),
+ ]);
+
+ expect(parseXML(xml)).toEqual(expected);
+ });
+
+ it('with folders filtering', () => {
+ const xml = createElement('doc', [
+ createElement('mailbox_list', [
+ createElement('details'),
+ ...messageElements,
+ ]),
+ createElement('folder_list', folderElements),
+ ]);
+
+ const items = parseXML(xml);
+ const expected = messages[1];
+ const result = items[0];
+
+ expect(items.length).toBe(1);
+ expect(result).toEqual({
+ ...expected,
+ date: expected.date.toString(),
+ from: expected.from.name,
+ subject: '',
+ });
});
+ });
});
diff --git a/src/pages/background/utils/__tests__/session-generator.test.js b/src/pages/background/utils/__tests__/session-generator.test.js
index 486f430..ae13e34 100644
--- a/src/pages/background/utils/__tests__/session-generator.test.js
+++ b/src/pages/background/utils/__tests__/session-generator.test.js
@@ -3,8 +3,8 @@ import sessionId from '../session-generator';
const chars = '[0-9A-F]';
describe('utils/session-generator', () => {
- it('output format is valid', () => {
- const regexp = new RegExp(`^${chars}{8}-${chars}{4}-${chars}{4}-${chars}{4}-${chars}{12}$`);
- expect(sessionId).toEqual(expect.stringMatching(regexp));
- });
+ it('output format is valid', () => {
+ const regexp = new RegExp(`^${chars}{8}-${chars}{4}-${chars}{4}-${chars}{4}-${chars}{12}$`);
+ expect(sessionId).toEqual(expect.stringMatching(regexp));
+ });
});
diff --git a/src/pages/background/utils/__tests__/url-resolver.test.js b/src/pages/background/utils/__tests__/url-resolver.test.js
index b89a658..21ace59 100644
--- a/src/pages/background/utils/__tests__/url-resolver.test.js
+++ b/src/pages/background/utils/__tests__/url-resolver.test.js
@@ -2,24 +2,24 @@ import resolveUrl from '../url-resolver';
import store from '../../redux/store';
jest.mock('../../redux/store', () => ({
- getState: jest.fn(() => ({
- settings: {
- preferredDomain: 'testdomain',
- },
- })),
+ getState: jest.fn(() => ({
+ settings: {
+ preferredDomain: 'testdomain',
+ },
+ })),
}));
describe('utils/url-resolve', () => {
- it('doesnt change string without pattern', () => {
- const input = 'https://link-without-pattern.com';
+ it('doesnt change string without pattern', () => {
+ const input = 'https://link-without-pattern.com';
- expect(resolveUrl(input)).toBe(input);
- });
+ expect(resolveUrl(input)).toBe(input);
+ });
- it('change string with pattern', () => {
- const input = 'https://link-without-pattern.{domain}';
- const domain = store.getState().settings.preferredDomain;
+ it('change string with pattern', () => {
+ const input = 'https://link-without-pattern.{domain}';
+ const domain = store.getState().settings.preferredDomain;
- expect(resolveUrl(input)).toBe('https://link-without-pattern.{domain}'.replace('{domain}', domain));
- });
+ expect(resolveUrl(input)).toBe('https://link-without-pattern.{domain}'.replace('{domain}', domain));
+ });
});
diff --git a/src/pages/background/utils/api.js b/src/pages/background/utils/api.js
index f88de9c..50d4209 100644
--- a/src/pages/background/utils/api.js
+++ b/src/pages/background/utils/api.js
@@ -1,165 +1,165 @@
import request from 'superagent';
import appConfig from 'shared/config';
import {
- getUid as getCookieUid,
- getSessionId,
+ getUid as getCookieUid,
+ getSessionId,
} from './cookie';
import resolveUrl from './url-resolver';
import parseXML from './parser';
const API_URL = 'https://mail.yandex.{domain}/api';
const AUTH_CONFIG = {
- tokenUrl: 'https://oauth.yandex.{domain}/token',
- passportUrl: 'https://pass.yandex.{domain}/accounts',
- clientId: '49c545918c574ac28dd7d27e8297065a',
- clientSecret: '813caaea334a4fb5be54a8b9af3f4c97',
+ tokenUrl: 'https://oauth.yandex.{domain}/token',
+ passportUrl: 'https://pass.yandex.{domain}/accounts',
+ clientId: '49c545918c574ac28dd7d27e8297065a',
+ clientSecret: '813caaea334a4fb5be54a8b9af3f4c97',
};
// prevent sending 'Origin' header, otherwise yandex doesn't execute an operation
chrome.webRequest.onBeforeSendHeaders.addListener(({requestHeaders}) => ({
- requestHeaders: requestHeaders.filter(({name}) => name !== 'Origin'),
+ requestHeaders: requestHeaders.filter(({name}) => name !== 'Origin'),
}), {
- urls: appConfig.supportedDomains.map(domain => `${API_URL}/*`.replace('{domain}', domain)),
+ urls: appConfig.supportedDomains.map(domain => `${API_URL}/*`.replace('{domain}', domain)),
}, ['blocking', 'requestHeaders']);
async function sendRequest(data) {
- const {
- method = 'get',
- url,
- type = 'json',
- form,
- query,
- } = data;
-
- const res = await request(method, url)
- .type(form ? 'form' : 'json')
- .send(form)
- .query(query);
+ const {
+ method = 'get',
+ url,
+ type = 'json',
+ form,
+ query,
+ } = data;
+
+ const res = await request(method, url)
+ .type(form ? 'form' : 'json')
+ .send(form)
+ .query(query);
// json
- if (type === 'json') {
- const resData = res.body || JSON.parse(res.text);
+ if (type === 'json') {
+ const resData = res.body || JSON.parse(res.text);
- if (resData.code === appConfig.errors.notAuthorized) {
- throw new Error(appConfig.errors.notAuthorized);
- }
-
- return resData;
+ if (resData.code === appConfig.errors.notAuthorized) {
+ throw new Error(appConfig.errors.notAuthorized);
}
- // xml
- const {responseXML} = res.xhr;
-
- if (!responseXML) {
- throw new Error(`No XML in the response: ${res.text}`);
- }
-
- // sometimes Yandex sends "redirect" instead of result, so we need just follow that url in response
- // check on "redirect_to" should be before "error", because "redirect_to" response contains "error" as well
- const redirect = responseXML.querySelector('redirect_to');
- if (redirect) {
- return sendRequest({
- ...data,
- url: redirect.textContent,
- });
- }
+ return resData;
+ }
- const err = responseXML.querySelector('error');
- if (err) {
- const errText = err.textContent || err.getAttribute('code');
- throw new Error(`Error in the response: ${errText}`);
- }
+ // xml
+ const {responseXML} = res.xhr;
- return res.xhr.responseXML;
-}
+ if (!responseXML) {
+ throw new Error(`No XML in the response: ${res.text}`);
+ }
-async function loadUserInfo() {
- const cookieUid = await getCookieUid();
- const {
- default_uid: uid,
- accounts,
- } = await sendRequest({
- url: resolveUrl(AUTH_CONFIG.passportUrl),
- query: {
- yu: cookieUid,
- },
+ // sometimes Yandex sends "redirect" instead of result, so we need just follow that url in response
+ // check on "redirect_to" should be before "error", because "redirect_to" response contains "error" as well
+ const redirect = responseXML.querySelector('redirect_to');
+ if (redirect) {
+ return sendRequest({
+ ...data,
+ url: redirect.textContent,
});
+ }
- if (!accounts) {
- throw new Error(appConfig.errors.notAuthorized);
- }
+ const err = responseXML.querySelector('error');
+ if (err) {
+ const errText = err.textContent || err.getAttribute('code');
+ throw new Error(`Error in the response: ${errText}`);
+ }
- const user = accounts.find(item => item.uid === uid);
+ return res.xhr.responseXML;
+}
- return {
- uid,
- email: user.defaultEmail,
- };
+async function loadUserInfo() {
+ const cookieUid = await getCookieUid();
+ const {
+ default_uid: uid,
+ accounts,
+ } = await sendRequest({
+ url: resolveUrl(AUTH_CONFIG.passportUrl),
+ query: {
+ yu: cookieUid,
+ },
+ });
+
+ if (!accounts) {
+ throw new Error(appConfig.errors.notAuthorized);
+ }
+
+ const user = accounts.find(item => item.uid === uid);
+
+ return {
+ uid,
+ email: user.defaultEmail,
+ };
}
async function loadToken(uid) {
- const sessionId = await getSessionId();
- const res = await sendRequest({
- method: 'post',
- url: resolveUrl(AUTH_CONFIG.tokenUrl),
- form: {
- grant_type: 'sessionid',
- host: resolveUrl('yandex.{domain}'),
- client_id: AUTH_CONFIG.clientId,
- client_secret: AUTH_CONFIG.clientSecret,
- sessionid: sessionId,
- uid,
- },
- });
-
- return res.access_token;
+ const sessionId = await getSessionId();
+ const res = await sendRequest({
+ method: 'post',
+ url: resolveUrl(AUTH_CONFIG.tokenUrl),
+ form: {
+ grant_type: 'sessionid',
+ host: resolveUrl('yandex.{domain}'),
+ client_id: AUTH_CONFIG.clientId,
+ client_secret: AUTH_CONFIG.clientSecret,
+ sessionid: sessionId,
+ uid,
+ },
+ });
+
+ return res.access_token;
}
export async function loadUser(token) {
- const userInfo = await loadUserInfo();
+ const userInfo = await loadUserInfo();
- return {
- ...userInfo,
- token: token || await loadToken(userInfo.uid),
- };
+ return {
+ ...userInfo,
+ token: token || await loadToken(userInfo.uid),
+ };
}
export async function getMessagesCount() {
- const res = await sendRequest({
- url: resolveUrl(`${API_URL}/v2/bar/counters`),
- query: {silent: true},
- });
+ const res = await sendRequest({
+ url: resolveUrl(`${API_URL}/v2/bar/counters`),
+ query: {silent: true},
+ });
- if (!res.counters) {
- throw new Error(res);
- }
+ if (!res.counters) {
+ throw new Error(res);
+ }
- return res.counters.unread;
+ return res.counters.unread;
}
export async function getMessages() {
- const res = await sendRequest({
- url: resolveUrl(`${API_URL}/mailbox_list`),
- type: 'xml',
- query: {
- first: 0,
- last: 100,
- extra_cond: 'only_new',
- goto: 'all',
- },
- });
-
- return parseXML(res);
+ const res = await sendRequest({
+ url: resolveUrl(`${API_URL}/mailbox_list`),
+ type: 'xml',
+ query: {
+ first: 0,
+ last: 100,
+ extra_cond: 'only_new',
+ goto: 'all',
+ },
+ });
+
+ return parseXML(res);
}
export function updateMessageStatus({oper, id}) {
- return sendRequest({
- method: 'post',
- url: resolveUrl(`${API_URL}/mailbox_oper`),
- type: 'xml',
- form: {
- ids: [id],
- oper,
- },
- });
+ return sendRequest({
+ method: 'post',
+ url: resolveUrl(`${API_URL}/mailbox_oper`),
+ type: 'xml',
+ form: {
+ ids: [id],
+ oper,
+ },
+ });
}
diff --git a/src/pages/background/utils/cookie.js b/src/pages/background/utils/cookie.js
index f29f175..1ca8db2 100644
--- a/src/pages/background/utils/cookie.js
+++ b/src/pages/background/utils/cookie.js
@@ -1,44 +1,44 @@
import resolveUrl from './url-resolver';
const {
- runtime,
- cookies,
+ runtime,
+ cookies,
} = chrome;
export const config = {
- domain: '.yandex.{domain}',
- path: '/',
- items: {
- login: 'yandex_login',
- sessionId: 'Session_id',
- uid: 'yandexuid',
- },
+ domain: '.yandex.{domain}',
+ path: '/',
+ items: {
+ login: 'yandex_login',
+ sessionId: 'Session_id',
+ uid: 'yandexuid',
+ },
};
function getCookieByName(name) {
- return new Promise(resolve => {
- cookies.getAll({
- domain: resolveUrl(config.domain),
- path: config.path,
- name,
- }, res => {
- if (runtime.lastError) {
- throw new Error(`Last error in cookie ${runtime.lastError.message}`);
- }
+ return new Promise(resolve => {
+ cookies.getAll({
+ domain: resolveUrl(config.domain),
+ path: config.path,
+ name,
+ }, res => {
+ if (runtime.lastError) {
+ throw new Error(`Last error in cookie ${runtime.lastError.message}`);
+ }
- resolve(
- Array.isArray(res) &&
+ resolve(
+ Array.isArray(res) &&
res[0] &&
res[0].value,
- );
- });
+ );
});
+ });
}
export function getUid() {
- return getCookieByName(config.items.uid);
+ return getCookieByName(config.items.uid);
}
export function getSessionId() {
- return getCookieByName(config.items.sessionId);
+ return getCookieByName(config.items.sessionId);
}
diff --git a/src/pages/background/utils/parser.js b/src/pages/background/utils/parser.js
index 6bee30d..1ba4fb2 100644
--- a/src/pages/background/utils/parser.js
+++ b/src/pages/background/utils/parser.js
@@ -1,69 +1,69 @@
const IGNORED_FOLDERS = [
- 'spam',
- 'archive',
- 'trash',
- 'sent',
- 'outbox',
- 'draft',
+ 'spam',
+ 'archive',
+ 'trash',
+ 'sent',
+ 'outbox',
+ 'draft',
];
function getText(element, selector) {
- return element.querySelector(selector).textContent;
+ return element.querySelector(selector).textContent;
}
function getFilteredMessage(messages, folders) {
- return messages.filter(({fid}) => {
- const folder = folders.find(({id}) => id === fid);
+ return messages.filter(({fid}) => {
+ const folder = folders.find(({id}) => id === fid);
- return folder && !IGNORED_FOLDERS.includes(folder.symbol);
- });
+ return folder && !IGNORED_FOLDERS.includes(folder.symbol);
+ });
}
function parseFolders(folders) {
- return [...folders].map(folder => ({
- id: getText(folder, 'fid'),
- symbol: getText(folder, 'symbol'),
- }));
+ return [...folders].map(folder => ({
+ id: getText(folder, 'fid'),
+ symbol: getText(folder, 'symbol'),
+ }));
}
function parseMessages(messages) {
- return [...messages].map(message => {
- const from = message.querySelector('from');
- let subject = getText(message.querySelector('subject'), 'text');
+ return [...messages].map(message => {
+ const from = message.querySelector('from');
+ let subject = getText(message.querySelector('subject'), 'text');
- if (subject === 'No subject') {
- subject = '';
- }
+ if (subject === 'No subject') {
+ subject = '';
+ }
- return {
- subject,
- id: message.getAttribute('mid'),
- from: getText(from, 'name') || getText(from, 'email'),
- firstline: getText(message, 'firstline'),
- date: message.getAttribute('recv_date'),
- fid: message.getAttribute('fid'),
- };
- });
+ return {
+ subject,
+ id: message.getAttribute('mid'),
+ from: getText(from, 'name') || getText(from, 'email'),
+ firstline: getText(message, 'firstline'),
+ date: message.getAttribute('recv_date'),
+ fid: message.getAttribute('fid'),
+ };
+ });
}
export default function (xml) {
- let messages = xml.querySelector('mailbox_list');
- if (!messages) {
- throw new Error('Bad response format in messages xml');
- }
+ let messages = xml.querySelector('mailbox_list');
+ if (!messages) {
+ throw new Error('Bad response format in messages xml');
+ }
- let folders = xml.querySelector('folder_list');
- if (!folders) {
- throw new Error('Bad response format in folders xml');
- }
+ let folders = xml.querySelector('folder_list');
+ if (!folders) {
+ throw new Error('Bad response format in folders xml');
+ }
- const error = messages.querySelector('error');
- if (error) {
- throw new Error(`Messages xml has error field with code: ${error.getAttribute('code')}`);
- }
+ const error = messages.querySelector('error');
+ if (error) {
+ throw new Error(`Messages xml has error field with code: ${error.getAttribute('code')}`);
+ }
- messages = parseMessages(messages.querySelectorAll('message'));
- folders = parseFolders(folders.querySelectorAll('folder'));
+ messages = parseMessages(messages.querySelectorAll('message'));
+ folders = parseFolders(folders.querySelectorAll('folder'));
- return getFilteredMessage(messages, folders);
+ return getFilteredMessage(messages, folders);
}
diff --git a/src/pages/background/utils/runtime.js b/src/pages/background/utils/runtime.js
index f65f085..cf9a0a8 100644
--- a/src/pages/background/utils/runtime.js
+++ b/src/pages/background/utils/runtime.js
@@ -1,4 +1,4 @@
// eslint-disable-next-line import/prefer-default-export
export function reloadApp() {
- chrome.runtime.reload();
+ chrome.runtime.reload();
}
diff --git a/src/pages/background/utils/session-generator.js b/src/pages/background/utils/session-generator.js
index 926cf4d..b2fff32 100644
--- a/src/pages/background/utils/session-generator.js
+++ b/src/pages/background/utils/session-generator.js
@@ -2,16 +2,16 @@ const mask = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const availableChars = '0123456789ABCDEF';
function generateSessionId() {
- return mask
- .split('')
- .map(char => {
- if (char === 'x') {
- return availableChars[Math.floor(16 * Math.random())];
- }
+ return mask
+ .split('')
+ .map(char => {
+ if (char === 'x') {
+ return availableChars[Math.floor(16 * Math.random())];
+ }
- return char;
- })
- .join('');
+ return char;
+ })
+ .join('');
}
export default generateSessionId();
diff --git a/src/pages/background/utils/tab.js b/src/pages/background/utils/tab.js
index 6d1d424..047487e 100644
--- a/src/pages/background/utils/tab.js
+++ b/src/pages/background/utils/tab.js
@@ -1,17 +1,17 @@
import resolveUrl from './url-resolver';
export function openUrl(url) {
- // when we use the function as a callback for click event, "url" will be an event object
- let finalUrl = typeof url !== 'string' ? '' : url;
+ // when we use the function as a callback for click event, "url" will be an event object
+ let finalUrl = typeof url !== 'string' ? '' : url;
- // e.g. donation link is absolute however others are relative
- if (!finalUrl.includes('http')) {
- finalUrl = resolveUrl(`https://mail.yandex.{domain}/${finalUrl}`);
- }
+ // e.g. donation link is absolute however others are relative
+ if (!finalUrl.includes('http')) {
+ finalUrl = resolveUrl(`https://mail.yandex.{domain}/${finalUrl}`);
+ }
- chrome.tabs.create({url: finalUrl});
+ chrome.tabs.create({url: finalUrl});
}
export function openSettings() {
- chrome.runtime.openOptionsPage();
+ chrome.runtime.openOptionsPage();
}
diff --git a/src/pages/background/utils/url-resolver.js b/src/pages/background/utils/url-resolver.js
index ba5db9d..e79e0ff 100644
--- a/src/pages/background/utils/url-resolver.js
+++ b/src/pages/background/utils/url-resolver.js
@@ -1,7 +1,7 @@
import store from '../redux/store';
export default function (url) {
- const currentDomain = store.getState().settings.preferredDomain;
+ const currentDomain = store.getState().settings.preferredDomain;
- return url.replace('{domain}', currentDomain);
+ return url.replace('{domain}', currentDomain);
}
diff --git a/src/pages/popup/actions.js b/src/pages/popup/actions.js
index 63c7dde..a59bd83 100644
--- a/src/pages/popup/actions.js
+++ b/src/pages/popup/actions.js
@@ -1,51 +1,51 @@
import {
- LOAD_MESSAGES,
- UPDATE_MESSAGE,
+ LOAD_MESSAGES,
+ UPDATE_MESSAGE,
} from 'shared/redux-consts/messages';
import {
- OPEN_LINK,
- OPEN_SETTINGS,
- RELOAD_APP,
- OPEN_DONATION_LINK,
+ OPEN_LINK,
+ OPEN_SETTINGS,
+ RELOAD_APP,
+ OPEN_DONATION_LINK,
} from 'shared/redux-consts/popup';
// only FF needs it
function closePopup() {
- window.close();
+ window.close();
}
export function loadMessages() {
- return {type: LOAD_MESSAGES};
+ return {type: LOAD_MESSAGES};
}
export function updateMessage(data) {
- return {
- type: UPDATE_MESSAGE,
- data,
- };
+ return {
+ type: UPDATE_MESSAGE,
+ data,
+ };
}
export function openLink(url) {
- closePopup();
+ closePopup();
- return {
- type: OPEN_LINK,
- url,
- };
+ return {
+ type: OPEN_LINK,
+ url,
+ };
}
export function openSettings() {
- closePopup();
+ closePopup();
- return {type: OPEN_SETTINGS};
+ return {type: OPEN_SETTINGS};
}
export function reloadApp() {
- return {type: RELOAD_APP};
+ return {type: RELOAD_APP};
}
export function openDonationLink() {
- closePopup();
+ closePopup();
- return {type: OPEN_DONATION_LINK};
+ return {type: OPEN_DONATION_LINK};
}
diff --git a/src/pages/popup/components/App.js b/src/pages/popup/components/App.js
index 517fee1..21545f1 100644
--- a/src/pages/popup/components/App.js
+++ b/src/pages/popup/components/App.js
@@ -8,90 +8,90 @@ import Header from './Header/Header';
import List from './List/List';
class App extends Component {
- static propTypes = {
- user: PropTypes.shape({
- authorized: PropTypes.bool.isRequired,
- email: PropTypes.string.isRequired,
- }).isRequired,
- messages: PropTypes.shape({
- unreadCount: PropTypes.number.isRequired,
- items: PropTypes.arrayOf(PropTypes.object).isRequired,
- loading: PropTypes.bool.isRequired,
- error: PropTypes.bool.isRequired,
- }).isRequired,
- settings: PropTypes.shape({
- preferredDomain: PropTypes.string.isRequired,
- }).isRequired,
- loadMessages: PropTypes.func.isRequired,
- updateMessage: PropTypes.func.isRequired,
- openLink: PropTypes.func.isRequired,
- openSettings: PropTypes.func.isRequired,
- reloadApp: PropTypes.func.isRequired,
- openDonationLink: PropTypes.func.isRequired,
- };
+ static propTypes = {
+ user: PropTypes.shape({
+ authorized: PropTypes.bool.isRequired,
+ email: PropTypes.string.isRequired,
+ }).isRequired,
+ messages: PropTypes.shape({
+ unreadCount: PropTypes.number.isRequired,
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ loading: PropTypes.bool.isRequired,
+ error: PropTypes.bool.isRequired,
+ }).isRequired,
+ settings: PropTypes.shape({
+ preferredDomain: PropTypes.string.isRequired,
+ }).isRequired,
+ loadMessages: PropTypes.func.isRequired,
+ updateMessage: PropTypes.func.isRequired,
+ openLink: PropTypes.func.isRequired,
+ openSettings: PropTypes.func.isRequired,
+ reloadApp: PropTypes.func.isRequired,
+ openDonationLink: PropTypes.func.isRequired,
+ };
- componentDidMount() {
- const {
- user,
- loadMessages,
- } = this.props;
+ componentDidMount() {
+ const {
+ user,
+ loadMessages,
+ } = this.props;
- if (user.authorized) {
- loadMessages();
- }
+ if (user.authorized) {
+ loadMessages();
}
+ }
- render() {
- const {
- user,
- messages: {
- unreadCount,
- items,
- loading,
- error,
- },
- settings: {
- preferredDomain,
- },
- loadMessages,
- updateMessage,
- openLink,
- openSettings,
- reloadApp,
- openDonationLink,
- } = this.props;
+ render() {
+ const {
+ user,
+ messages: {
+ unreadCount,
+ items,
+ loading,
+ error,
+ },
+ settings: {
+ preferredDomain,
+ },
+ loadMessages,
+ updateMessage,
+ openLink,
+ openSettings,
+ reloadApp,
+ openDonationLink,
+ } = this.props;
- if (!user.authorized) {
- return (
-
- {from} - {i18n.date(date)} -
-{subject}
-+ {from} + {i18n.date(date)} +
+{subject}
+{firstline}
-{firstline}
+