From deb177c6bb239b71cde393b476e0c6b00322fff4 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 7 Feb 2018 12:09:23 -0800 Subject: [PATCH 0001/1871] updated availableLanguages and localized progress percentage. fixes #747 --- app/templates/progress.js | 6 +++--- package.json | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/templates/progress.js b/app/templates/progress.js index fe0cf6b73..0e48dc955 100644 --- a/app/templates/progress.js +++ b/app/templates/progress.js @@ -1,4 +1,5 @@ const html = require('choo/html'); +const percent = require('../utils').percent; const radius = 73; const oRadius = radius + 10; @@ -7,7 +8,7 @@ const circumference = 2 * Math.PI * radius; module.exports = function(progressRatio) { const dashOffset = (1 - progressRatio) * circumference; - const percent = Math.floor(progressRatio * 100); + const percentComplete = percent(progressRatio); const div = html`
- ${percent} - % + ${percentComplete}
diff --git a/package.json b/package.json index 50fecee58..198bee4aa 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ }, "availableLanguages": [ "en-US", + "ar", "ast", "az", "bs", @@ -131,6 +132,7 @@ "fy-NL", "hsb", "hu", + "ia", "id", "it", "ja", @@ -143,9 +145,11 @@ "nn-NO", "pt-BR", "pt-PT", + "ro", "ru", "sk", "sl", + "sq", "sr", "sv-SE", "tl", From a78150b7ad468090dbbbde4f7166916b51e87aa4 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 7 Feb 2018 18:52:41 -0800 Subject: [PATCH 0002/1871] disable nanotiming --- app/routes/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/routes/index.js b/app/routes/index.js index 67818e58b..df41d0d3c 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -1,3 +1,14 @@ +try { + // It's kind of silly that this needs to be here... + // 'nanotiming' reads this *on initialization* so + // the only way to make sure it's disabled from the + // first page load is to set it before we even + // require('choo') + localStorage.setItem('DISABLE_NANOTIMING', true); +} catch (e) { + // localStorage is disabled. nanotiming will also be +} + const choo = require('choo'); const html = require('choo/html'); const download = require('./download'); From a777a808eea1bb1d7cd4bd714d5c1f89fd48bca5 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 7 Feb 2018 19:18:11 -0800 Subject: [PATCH 0003/1871] renamed localization ctx global to reduce the chance of collision (seen in sentry) --- build/fluent_loader.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/fluent_loader.js b/build/fluent_loader.js index 26d6ee564..a19198772 100644 --- a/build/fluent_loader.js +++ b/build/fluent_loader.js @@ -38,14 +38,14 @@ module.exports = \` if (typeof window === 'undefined') { var fluent = require('fluent'); } -var ctx = new fluent.MessageContext('${locale}', {useIsolating: false}); -ctx._messages = new Map(${toJSON(merged)}); +var fluentContext = new fluent.MessageContext('${locale}', {useIsolating: false}); +fluentContext._messages = new Map(${toJSON(merged)}); function translate(id, data) { - var msg = ctx.getMessage(id); + var msg = fluentContext.getMessage(id); if (typeof(msg) !== 'string' && !msg.val && msg.attrs) { msg = msg.attrs.title || msg.attrs.alt } - return ctx.format(msg, data); + return fluentContext.format(msg, data); } if (typeof window === 'undefined') { module.exports = translate; From cf36a33aea844062a391b989656d91fd56ee10ca Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 7 Feb 2018 19:46:18 -0800 Subject: [PATCH 0004/1871] localize other displayed numbers --- app/templates/file.js | 5 ++++- app/templates/selectbox.js | 5 ++++- app/utils.js | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/templates/file.js b/app/templates/file.js index 573302eb4..d6558de5a 100644 --- a/app/templates/file.js +++ b/app/templates/file.js @@ -1,5 +1,6 @@ const html = require('choo/html'); const assets = require('../../common/assets'); +const number = require('../utils').number; function timeLeft(milliseconds, state) { const minutes = Math.floor(milliseconds / 1000 / 60); @@ -40,7 +41,9 @@ module.exports = function(file, state, emit) { ${remainingTime} - ${totalDownloads} / ${downloadLimit} + ${number(totalDownloads)} / ${number( + downloadLimit + )} ${options.map( i => - html`
  • ${i}
  • ` + html`
  • ${number( + i + )}
  • ` )} `; diff --git a/app/utils.js b/app/utils.js index 9fea560c0..e6d8d27b5 100644 --- a/app/utils.js +++ b/app/utils.js @@ -117,6 +117,14 @@ function percent(ratio) { return `${Math.floor(ratio * 100)}%`; } +function number(n) { + if (LOCALIZE_NUMBERS) { + const locale = document.querySelector('html').lang; + return n.toLocaleString(locale); + } + return n.toString(); +} + function allowedCopy() { const support = !!document.queryCommandSupported; return support ? document.queryCommandSupported('copy') : false; @@ -171,6 +179,7 @@ module.exports = { allowedCopy, bytes, percent, + number, copyToClipboard, arrayToB64, b64ToArray, From caaa613ce930cc1d089004b5c80c73895432cc25 Mon Sep 17 00:00:00 2001 From: Mozilla Pontoon Date: Thu, 8 Feb 2018 07:50:49 +0000 Subject: [PATCH 0005/1871] Pontoon: Update Interlingua (ia) localization of Test Pilot: Firefox Send --- public/locales/ia/send.ftl | 1 - 1 file changed, 1 deletion(-) diff --git a/public/locales/ia/send.ftl b/public/locales/ia/send.ftl index c26f0bd90..dc52ea250 100644 --- a/public/locales/ia/send.ftl +++ b/public/locales/ia/send.ftl @@ -89,7 +89,6 @@ legalHeader = Terminos & confidentialitate legalNoticeTestPilot = Firefox Send es actualmente un Experimento pilota, e subjecte a Terminos de servicio e Notitia de confidentialitate de Experimento pilota. Tu pote saper plus re iste experimento e su recolta de datos hic. legalNoticeMozilla = Le uso del sito web de Firefox Send es anque subjecte a Notitia de confidentialitate de sito web e Terminos de servicio sito web. deletePopupText = Deler iste file? -deletePopupYes = deletePopupCancel = Cancellar deleteButtonHover = Deler copyUrlHover = Copiar le URL From 71d4566df5403f65564bfdcda31efa6ea0620dd9 Mon Sep 17 00:00:00 2001 From: Mozilla Pontoon Date: Thu, 8 Feb 2018 07:50:52 +0000 Subject: [PATCH 0006/1871] Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send --- public/locales/cak/send.ftl | 1 - 1 file changed, 1 deletion(-) diff --git a/public/locales/cak/send.ftl b/public/locales/cak/send.ftl index 201562f53..c45fe379d 100644 --- a/public/locales/cak/send.ftl +++ b/public/locales/cak/send.ftl @@ -107,7 +107,6 @@ passwordTryAgain = Itzel ri ewan tzij. Tatojtob'ej chik. passwordResult = Ewan tzij: { $password } reportIPInfringement = Tiya' rutzijol ri Ritzelanik Ajna'oj Ichinil javascriptRequired = K'atzinel JavaScript chi re ri Firefox Send -whyJavascript = enableJavascript = Titz'ij JavaScript richin nitojtob'ëx chik. // A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" expiresHoursMinutes = { $hours }r { $minutes }ch From 35398686836cfaa9f78302a8894caf3ecb737142 Mon Sep 17 00:00:00 2001 From: Mozilla Pontoon Date: Thu, 8 Feb 2018 07:50:55 +0000 Subject: [PATCH 0007/1871] Pontoon: Update Romanian (ro) localization of Test Pilot: Firefox Send --- public/locales/ro/send.ftl | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/locales/ro/send.ftl b/public/locales/ro/send.ftl index 2c0555df4..9ae4730fc 100644 --- a/public/locales/ro/send.ftl +++ b/public/locales/ro/send.ftl @@ -17,7 +17,6 @@ verifyingFile = Se verifică... encryptingFile = Se criptează… decryptingFile = Se decriptează… notifyUploadDone = Încărcarea s-a finalizat. -uploadingPageMessage = uploadingPageCancel = Anulează încărcarea uploadCancelNotification = Încărcarea a fost anulată. uploadingPageLargeFileMessage = Stai calm! Acest fișier este mare. S-ar putea să dureze un timp încărcarea. @@ -26,7 +25,6 @@ uploadSuccessConfirmHeader = Pregătit pentru trimitere uploadSvgAlt = Încarcă uploadSuccessTimingHeader = Linkul către fișierul tău va expira după 1 descărcare sau în 24 de ore. expireInfo = Linkul la fișier va expira după { $downloadCount } sau { $timespan }. -downloadCount = timespanHours = { $num -> [one] 1 oră [few] ore From 629a86de997e45dd73dd1557c5b6a7edb9837a95 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Thu, 8 Feb 2018 15:31:55 +0000 Subject: [PATCH 0008/1871] Pontoon: Update Albanian (sq) localization of Test Pilot: Firefox Send Localization authors: - Besnik Bleta --- public/locales/sq/send.ftl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/public/locales/sq/send.ftl b/public/locales/sq/send.ftl index 196f00251..6e3320dde 100644 --- a/public/locales/sq/send.ftl +++ b/public/locales/sq/send.ftl @@ -41,6 +41,12 @@ deleteFileButton = Fshije kartelën sendAnotherFileLink = Dërgoni një kartelë tjetër // Alternative text used on the download link/button (indicates an action). downloadAltText = Shkarkoje +downloadsFileList = Shkarkime +// Used as header in a column indicating the amount of time left before a +// download link expires (e.g. "10h 5m") +timeFileList = Kohë +// Used as header in a column indicating the number of times a file has been +// downloaded downloadFileName = Shkarkoje { $filename } downloadFileSize = ({ $size }) unlockInputLabel = Jepni Fjalëkalimin @@ -95,7 +101,15 @@ footerLinkTerms = Kushte footerLinkCookies = Cookies requirePasswordCheckbox = Kërko doemos një fjalëkalim për shkarkim të kësaj kartele addPasswordButton = Shtoni fjalëkalim +changePasswordButton = Ndryshoje passwordTryAgain = Fjalëkalim i pasaktë. Riprovoni. // This label is followed by the password needed to download a file passwordResult = Fjalëkalim: { $password } reportIPInfringement = Raportoni Cenim IP-je +javascriptRequired = Firefox Send lyp JavaScript +whyJavascript = Ç’i duhet Firefox Send-it JavaScript-i? +enableJavascript = Ju lutemi, aktivizoni JavaScript-in dhe riprovoni. +// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" +expiresHoursMinutes = { $hours }h { $minutes }m +// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" +expiresMinutes = { $minutes }m From 9a1852ea05e93a5cb92390f7345c7379662befc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Esteban=20Ajsivinac=20Si=C3=A1n?= Date: Thu, 8 Feb 2018 20:10:59 +0000 Subject: [PATCH 0009/1871] Pontoon: Update Kaqchikel (cak) localization of Test Pilot: Firefox Send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localization authors: - Juan Esteban Ajsivinac Sián --- public/locales/cak/send.ftl | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/cak/send.ftl b/public/locales/cak/send.ftl index c45fe379d..bc42a03cd 100644 --- a/public/locales/cak/send.ftl +++ b/public/locales/cak/send.ftl @@ -107,6 +107,7 @@ passwordTryAgain = Itzel ri ewan tzij. Tatojtob'ej chik. passwordResult = Ewan tzij: { $password } reportIPInfringement = Tiya' rutzijol ri Ritzelanik Ajna'oj Ichinil javascriptRequired = K'atzinel JavaScript chi re ri Firefox Send +whyJavascript = ¿Achike ruma toq ri Firefox Send nrajo' JavaScript? enableJavascript = Titz'ij JavaScript richin nitojtob'ëx chik. // A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" expiresHoursMinutes = { $hours }r { $minutes }ch From 71ad81a67dfcc8ed91e452f3cbbd3ca11d234d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Fri, 9 Feb 2018 15:22:41 +0100 Subject: [PATCH 0010/1871] Update to fluent 0.4.3 --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fd9ff4db..6756d05f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4065,9 +4065,9 @@ "dev": true }, "fluent": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/fluent/-/fluent-0.4.2.tgz", - "integrity": "sha512-P2w5DeSRYkq80J2VsuFC4SRZaG/jglAgVbHkUgLAaRoQtdDw6vdJjr36/ZH2zKk7bBwubGdIBuavNAhOVeuQxw==" + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/fluent/-/fluent-0.4.3.tgz", + "integrity": "sha512-yyqQmC83pOc0yyVGMynzHVAkJp61KJu9Be1aGhkO0PirkgsqfjyAn/0271eA/exwwQvYgQuDhBVYXA0zGxwKaA==" }, "fluent-intl-polyfill": { "version": "0.1.0", diff --git a/package.json b/package.json index 198bee4aa..e4b12d16e 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "connect-busboy": "0.0.2", "convict": "^4.0.1", "express": "^4.16.2", - "fluent": "^0.4.1", + "fluent": "^0.4.3", "fluent-langneg": "^0.1.0", "helmet": "^3.10.0", "mkdirp": "^0.5.1", From 3fd25373113d5ae47d606eb6e505e090e18fbaed Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Tue, 6 Feb 2018 14:31:18 -0800 Subject: [PATCH 0011/1871] refactored server --- .dockerignore | 1 + .gitignore | 3 + circle.yml | 9 +- package-lock.json | 2046 +++++++++++++++++++++++++++++-- package.json | 14 +- server/languages.js | 16 - server/metadata.js | 13 + server/middleware/auth.js | 38 + server/middleware/language.js | 40 + server/middleware/owner.js | 22 + server/routes/delete.js | 15 +- server/routes/download.js | 24 +- server/routes/exists.js | 6 +- server/routes/index.js | 74 +- server/routes/info.js | 16 +- server/routes/metadata.js | 18 +- server/routes/params.js | 14 +- server/routes/password.js | 12 +- server/routes/upload.js | 6 +- server/storage.js | 285 ----- server/storage/fs.js | 50 + server/storage/index.js | 57 + server/storage/redis.js | 21 + server/storage/s3.js | 51 + test/unit/auth-tests.js | 105 ++ test/unit/aws.storage.test.js | 173 --- test/unit/delete-tests.js | 43 + test/unit/info-tests.js | 59 + test/unit/language-tests.js | 67 + test/unit/local.storage.test.js | 163 --- test/unit/metadata-tests.js | 65 + test/unit/owner-tests.js | 81 ++ test/unit/params-tests.js | 56 + test/unit/password-tests.js | 53 + test/unit/s3-tests.js | 154 +++ test/unit/storage-tests.js | 118 ++ 36 files changed, 3070 insertions(+), 918 deletions(-) delete mode 100644 server/languages.js create mode 100644 server/metadata.js create mode 100644 server/middleware/auth.js create mode 100644 server/middleware/language.js create mode 100644 server/middleware/owner.js delete mode 100644 server/storage.js create mode 100644 server/storage/fs.js create mode 100644 server/storage/index.js create mode 100644 server/storage/redis.js create mode 100644 server/storage/s3.js create mode 100644 test/unit/auth-tests.js delete mode 100644 test/unit/aws.storage.test.js create mode 100644 test/unit/delete-tests.js create mode 100644 test/unit/info-tests.js create mode 100644 test/unit/language-tests.js delete mode 100644 test/unit/local.storage.test.js create mode 100644 test/unit/metadata-tests.js create mode 100644 test/unit/owner-tests.js create mode 100644 test/unit/params-tests.js create mode 100644 test/unit/password-tests.js create mode 100644 test/unit/s3-tests.js create mode 100644 test/unit/storage-tests.js diff --git a/.dockerignore b/.dockerignore index eb85ddcc7..a67706a4d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ assets docs public test +coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore index 82ee25a10..1e6cbed38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules +coverage dist .idea +.DS_Store +.nyc_output \ No newline at end of file diff --git a/circle.yml b/circle.yml index af5343623..a386c2720 100644 --- a/circle.yml +++ b/circle.yml @@ -4,13 +4,6 @@ machine: services: - docker - redis - environment: - PATH: "/home/ubuntu/send/firefox:$PATH" - -dependencies: - pre: - - npm i -g get-firefox geckodriver nsp - - get-firefox --platform linux --extract --target /home/ubuntu/send deployment: latest: @@ -31,5 +24,5 @@ test: override: - npm run build - npm run lint - - npm test + - npm run test:ci - nsp check diff --git a/package-lock.json b/package-lock.json index 6756d05f7..fccae2b86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,15 @@ } } }, + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + } + }, "ajv": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.0.tgz", @@ -1911,6 +1920,45 @@ "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", "dev": true }, + "cli-table2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cli-table2/-/cli-table2-0.2.0.tgz", + "integrity": "sha1-LR738hig54biFFQFYtS9F3/jLZc=", + "dev": true, + "requires": { + "colors": "1.1.2", + "lodash": "3.10.1", + "string-width": "1.0.2" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -2781,6 +2829,12 @@ "array-find-index": "1.0.2" } }, + "cvss": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cvss/-/cvss-1.0.2.tgz", + "integrity": "sha1-32fpK/EqeW9J6Sh5nI2zunS5/NY=", + "dev": true + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -3340,6 +3394,23 @@ "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", "dev": true }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.2.4" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + } + } + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -5748,6 +5819,27 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.1.1.tgz", + "integrity": "sha512-LK6tQUR/VOkTI6ygAfWUKKP95I+e6M1h7N3PncGu1CATHCnex+CAv9ttR0lbHu1Uk2PXm/WoAHFo6JCGwMjVMw==", + "dev": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "husky": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", @@ -7924,6 +8016,12 @@ } } }, + "nodesecurity-npm-utils": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-6.0.0.tgz", + "integrity": "sha512-NLRle1woNaT2orR6fue2jNqkhxDTktgJj3sZxvR/8kp21pvOY7Gwlx5wvo0H8ZVPqdgd2nE2ADB9wDu5Cl8zNg==", + "dev": true + }, "nomnom": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", @@ -8067,6 +8165,75 @@ "which": "1.3.0" } }, + "nsp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nsp/-/nsp-3.1.0.tgz", + "integrity": "sha512-jtD2WMlmqWA3zSZTFog8MQkwUvXWdJD7ps7A30q8ImOIao0RFgZXbPPZ8O6yWaKxLbRxVUIoCfwvXyg7AVS+IA==", + "dev": true, + "requires": { + "chalk": "2.3.0", + "cli-table2": "0.2.0", + "cvss": "1.0.2", + "https-proxy-agent": "2.1.1", + "inquirer": "3.3.0", + "nodesecurity-npm-utils": "6.0.0", + "semver": "5.4.1", + "wreck": "12.5.1", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + } + } + } + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -8079,142 +8246,1736 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "oauth-sign": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", - "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "obuf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", - "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, - "on-load": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/on-load/-/on-load-3.3.4.tgz", - "integrity": "sha512-WhwvSW0hZndA7HjN1O3AypgaAahFPTOmJ43bQUTQHsiHhtp9/kk+WlMnd/skqBkWuiNcQqNwKo2ZiwwRWStJIA==", - "dev": true, - "requires": { - "global": "4.3.2", - "nanoassert": "1.1.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "1.1.0" - } - }, - "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", + "nyc": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.4.1.tgz", + "integrity": "sha512-5eCZpvaksFVjP2rt1r60cfXmt3MUtsQDw8bAzNqNEr4WLvUMLgiVENMf/B9bE9YAX0mGVvaGA3v9IS9ekNqB1Q==", "dev": true, "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-spinners": "0.1.2", - "object-assign": "4.1.1" + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.1", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "2.1.0", + "foreground-child": "1.5.6", + "glob": "7.1.2", + "istanbul-lib-coverage": "1.1.1", + "istanbul-lib-hook": "1.1.0", + "istanbul-lib-instrument": "1.9.1", + "istanbul-lib-report": "1.1.2", + "istanbul-lib-source-maps": "1.2.2", + "istanbul-reports": "1.1.3", + "md5-hex": "1.3.0", + "merge-source-map": "1.0.4", + "micromatch": "2.3.11", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "spawn-wrap": "1.4.2", + "test-exclude": "4.1.1", + "yargs": "10.0.3", + "yargs-parser": "8.0.0" }, "dependencies": { - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "align-text": { + "version": "0.1.4", + "bundled": true, "dev": true, "requires": { - "restore-cursor": "1.0.1" + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" } }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "amdefine": { + "version": "1.0.1", + "bundled": true, "dev": true }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - } + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.3", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "which": "1.3.0" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.2", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.1.1", + "semver": "5.4.1" + } + }, + "istanbul-lib-report": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.4", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "merge-source-map": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mimic-fn": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "semver": { + "version": "5.4.1", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "10.0.3", + "bundled": true, + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "8.0.0" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + } + } + }, + "yargs-parser": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "oauth-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", + "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "obuf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", + "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, + "on-load": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/on-load/-/on-load-3.3.4.tgz", + "integrity": "sha512-WhwvSW0hZndA7HjN1O3AypgaAahFPTOmJ43bQUTQHsiHhtp9/kk+WlMnd/skqBkWuiNcQqNwKo2ZiwwRWStJIA==", + "dev": true, + "requires": { + "global": "4.3.2", + "nanoassert": "1.1.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "opn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", + "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", + "dev": true, + "requires": { + "is-wsl": "1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "ora": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", + "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-spinners": "0.1.2", + "object-assign": "4.1.1" + }, + "dependencies": { + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + } } }, "original": { @@ -10668,9 +12429,9 @@ "integrity": "sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs=" }, "redis-mock": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/redis-mock/-/redis-mock-0.20.0.tgz", - "integrity": "sha1-lKOVhlurvOv1OLa1mUFyPkgHMBA=", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/redis-mock/-/redis-mock-0.21.0.tgz", + "integrity": "sha512-AkLaD8JFrcQKFOklYsbocdqZ2hof69VQKOhH4uh5hAQuu0b/TaSlHyM2LSmE+8ZOYc6dNJzuYkQ6vQ1CRM122w==", "dev": true }, "redis-parser": { @@ -13231,6 +14992,33 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "wreck": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/wreck/-/wreck-12.5.1.tgz", + "integrity": "sha512-l5DUGrc+yDyIflpty1x9XuMj1ehVjC/dTbF3/BasOO77xk0EdEa4M/DuOY8W88MQDAD0fEDqyjc8bkIMHd2E9A==", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "dev": true + } + } + }, "write": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", diff --git a/package.json b/package.json index e4b12d16e..4b1f35946 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,10 @@ "contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS", "release": "npm-run-all contributors changelog", "test": "mocha test/unit", + "test:ci": "nyc mocha --reporter=min test/unit", "start": "cross-env NODE_ENV=development webpack-dev-server", - "prod": "node server/prod.js" + "prod": "node server/prod.js", + "cover": "nyc --reporter=html mocha test/unit" }, "lint-staged": { "*.js": [ @@ -39,6 +41,12 @@ "git add" ] }, + "nyc": { + "reporter": [ + "text-summary" + ], + "cache": true + }, "engines": { "node": ">=8.2.0" }, @@ -73,11 +81,13 @@ "mocha": "^5.0.0", "nanobus": "^4.3.2", "npm-run-all": "^4.1.2", + "nsp": "^3.1.0", + "nyc": "^11.4.1", "postcss-loader": "^2.1.0", "prettier": "^1.10.2", "proxyquire": "^1.8.0", "raven-js": "^3.22.1", - "redis-mock": "^0.20.0", + "redis-mock": "^0.21.0", "require-from-string": "^2.0.1", "rimraf": "^2.6.2", "selenium-webdriver": "^3.6.0", diff --git a/server/languages.js b/server/languages.js deleted file mode 100644 index d9121eefb..000000000 --- a/server/languages.js +++ /dev/null @@ -1,16 +0,0 @@ -const { availableLanguages } = require('../package.json'); -const config = require('./config'); -const fs = require('fs'); -const path = require('path'); - -function allLangs() { - return fs.readdirSync( - path.join(__dirname, '..', 'dist', 'public', 'locales') - ); -} - -if (config.l10n_dev) { - module.exports = allLangs(); -} else { - module.exports = availableLanguages; -} diff --git a/server/metadata.js b/server/metadata.js new file mode 100644 index 000000000..1c5993169 --- /dev/null +++ b/server/metadata.js @@ -0,0 +1,13 @@ +class Metadata { + constructor(obj) { + this.dl = +obj.dl || 0; + this.dlimit = +obj.dlimit || 1; + this.pwd = String(obj.pwd) === 'true'; + this.owner = obj.owner; + this.metadata = obj.metadata; + this.auth = obj.auth; + this.nonce = obj.nonce; + } +} + +module.exports = Metadata; diff --git a/server/middleware/auth.js b/server/middleware/auth.js new file mode 100644 index 000000000..b1a1607d7 --- /dev/null +++ b/server/middleware/auth.js @@ -0,0 +1,38 @@ +const crypto = require('crypto'); +const storage = require('../storage'); + +module.exports = async function(req, res, next) { + const id = req.params.id; + if (id && req.header('Authorization')) { + try { + const auth = req.header('Authorization').split(' ')[1]; + const meta = await storage.metadata(id); + if (!meta) { + return res.sendStatus(404); + } + const hmac = crypto.createHmac( + 'sha256', + Buffer.from(meta.auth, 'base64') + ); + hmac.update(Buffer.from(meta.nonce, 'base64')); + const verifyHash = hmac.digest(); + if (verifyHash.equals(Buffer.from(auth, 'base64'))) { + req.nonce = crypto.randomBytes(16).toString('base64'); + storage.setField(id, 'nonce', req.nonce); + res.set('WWW-Authenticate', `send-v1 ${req.nonce}`); + req.authorized = true; + req.meta = meta; + } else { + res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`); + req.authorized = false; + } + } catch (e) { + req.authorized = false; + } + } + if (req.authorized) { + next(); + } else { + res.sendStatus(401); + } +}; diff --git a/server/middleware/language.js b/server/middleware/language.js new file mode 100644 index 000000000..882826422 --- /dev/null +++ b/server/middleware/language.js @@ -0,0 +1,40 @@ +const { availableLanguages } = require('../../package.json'); +const config = require('../config'); +const fs = require('fs'); +const path = require('path'); +const { negotiateLanguages } = require('fluent-langneg'); +const langData = require('cldr-core/supplemental/likelySubtags.json'); +const acceptLanguages = /(([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?/g; + +function allLangs() { + return fs.readdirSync( + path.join(__dirname, '..', '..', 'dist', 'public', 'locales') + ); +} + +const languages = config.l10n_dev ? allLangs() : availableLanguages; + +module.exports = function(req, res, next) { + const header = req.headers['accept-language'] || 'en-US'; + if (header.length > 255) { + req.language = 'en-US'; + return next(); + } + const langs = header.replace(/\s/g, '').match(acceptLanguages); + const preferred = langs + .map(l => { + const parts = l.split(';'); + return { + locale: parts[0], + q: parts[1] ? parseFloat(parts[1].split('=')[1]) : 1 + }; + }) + .sort((a, b) => b.q - a.q) + .map(x => x.locale); + req.language = negotiateLanguages(preferred, languages, { + strategy: 'lookup', + likelySubtags: langData.supplemental.likelySubtags, + defaultLocale: 'en-US' + })[0]; + next(); +}; diff --git a/server/middleware/owner.js b/server/middleware/owner.js new file mode 100644 index 000000000..eb66562dd --- /dev/null +++ b/server/middleware/owner.js @@ -0,0 +1,22 @@ +const storage = require('../storage'); + +module.exports = async function(req, res, next) { + const id = req.params.id; + const ownerToken = req.body.owner_token; + if (id && ownerToken) { + try { + req.meta = await storage.metadata(id); + if (!req.meta) { + return res.sendStatus(404); + } + req.authorized = req.meta.owner === ownerToken; + } catch (e) { + req.authorized = false; + } + } + if (req.authorized) { + next(); + } else { + res.sendStatus(401); + } +}; diff --git a/server/routes/delete.js b/server/routes/delete.js index 080b7a696..71ada5f9e 100644 --- a/server/routes/delete.js +++ b/server/routes/delete.js @@ -1,20 +1,9 @@ const storage = require('../storage'); module.exports = async function(req, res) { - const id = req.params.id; - - const ownerToken = req.body.owner_token || req.body.delete_token; - - if (!ownerToken) { - res.sendStatus(404); - return; - } - try { - const err = await storage.delete(id, ownerToken); - if (!err) { - res.sendStatus(200); - } + await storage.del(req.params.id); + res.sendStatus(200); } catch (e) { res.sendStatus(404); } diff --git a/server/routes/download.js b/server/routes/download.js index 56a0f23ee..bfd667edd 100644 --- a/server/routes/download.js +++ b/server/routes/download.js @@ -1,39 +1,26 @@ const storage = require('../storage'); const mozlog = require('../log'); const log = mozlog('send.download'); -const crypto = require('crypto'); module.exports = async function(req, res) { const id = req.params.id; - try { - const auth = req.header('Authorization').split(' ')[1]; - const meta = await storage.metadata(id); - const hmac = crypto.createHmac('sha256', Buffer.from(meta.auth, 'base64')); - hmac.update(Buffer.from(meta.nonce, 'base64')); - const verifyHash = hmac.digest(); - if (!verifyHash.equals(Buffer.from(auth, 'base64'))) { - res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`); - return res.sendStatus(401); - } - const nonce = crypto.randomBytes(16).toString('base64'); - storage.setField(id, 'nonce', nonce); + const meta = req.meta; const contentLength = await storage.length(id); res.writeHead(200, { 'Content-Disposition': 'attachment', 'Content-Type': 'application/octet-stream', 'Content-Length': contentLength, - 'X-File-Metadata': meta.metadata, - 'WWW-Authenticate': `send-v1 ${nonce}` + 'WWW-Authenticate': `send-v1 ${req.nonce}` }); const file_stream = storage.get(id); file_stream.on('end', async () => { - const dl = (+meta.dl || 0) + 1; - const dlimit = +meta.dlimit || 1; + const dl = meta.dl + 1; + const dlimit = meta.dlimit; try { if (dl >= dlimit) { - await storage.forceDelete(id); + await storage.del(id); } else { await storage.setField(id, 'dl', dl); } @@ -41,7 +28,6 @@ module.exports = async function(req, res) { log.info('StorageError:', id); } }); - file_stream.pipe(res); } catch (e) { res.sendStatus(404); diff --git a/server/routes/exists.js b/server/routes/exists.js index c8039887c..badb32c3a 100644 --- a/server/routes/exists.js +++ b/server/routes/exists.js @@ -1,13 +1,11 @@ const storage = require('../storage'); module.exports = async (req, res) => { - const id = req.params.id; - try { - const meta = await storage.metadata(id); + const meta = await storage.metadata(req.params.id); res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`); res.send({ - password: meta.pwd !== '0' + password: meta.pwd }); } catch (e) { res.sendStatus(404); diff --git a/server/routes/index.js b/server/routes/index.js index f2c7e79d7..1d0229070 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,41 +1,22 @@ const busboy = require('connect-busboy'); const helmet = require('helmet'); const bodyParser = require('body-parser'); -const languages = require('../languages'); const storage = require('../storage'); const config = require('../config'); +const auth = require('../middleware/auth'); +const owner = require('../middleware/owner'); +const language = require('../middleware/language'); const pages = require('./pages'); -const { negotiateLanguages } = require('fluent-langneg'); + const IS_DEV = config.env === 'development'; -const acceptLanguages = /(([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?/g; -const langData = require('cldr-core/supplemental/likelySubtags.json'); -const idregx = '([0-9a-fA-F]{10})'; +const ID_REGEX = '([0-9a-fA-F]{10})'; +const uploader = busboy({ + limits: { + fileSize: config.max_file_size + } +}); module.exports = function(app) { - app.use(function(req, res, next) { - const header = req.headers['accept-language'] || 'en-US'; - if (header.length > 255) { - req.language = 'en-US'; - return next(); - } - const langs = header.replace(/\s/g, '').match(acceptLanguages); - const preferred = langs - .map(l => { - const parts = l.split(';'); - return { - locale: parts[0], - q: parts[1] ? parseFloat(parts[1].split('=')[1]) : 1 - }; - }) - .sort((a, b) => b.q - a.q) - .map(x => x.locale); - req.language = negotiateLanguages(preferred, languages, { - strategy: 'lookup', - likelySubtags: langData.supplemental.likelySubtags, - defaultLocale: 'en-US' - })[0]; - next(); - }); app.use(helmet()); app.use( helmet.hsts({ @@ -69,34 +50,27 @@ module.exports = function(app) { }) ); } - app.use( - busboy({ - limits: { - fileSize: config.max_file_size - } - }) - ); app.use(function(req, res, next) { res.set('Pragma', 'no-cache'); res.set('Cache-Control', 'no-cache'); next(); }); app.use(bodyParser.json()); - app.get('/', pages.index); - app.get('/legal', pages.legal); + app.get('/', language, pages.index); + app.get('/legal', language, pages.legal); app.get('/jsconfig.js', require('./jsconfig')); - app.get(`/share/:id${idregx}`, pages.blank); - app.get(`/download/:id${idregx}`, pages.download); - app.get('/completed', pages.blank); - app.get('/unsupported/:reason', pages.unsupported); - app.get(`/api/download/:id${idregx}`, require('./download')); - app.get(`/api/exists/:id${idregx}`, require('./exists')); - app.get(`/api/metadata/:id${idregx}`, require('./metadata')); - app.post('/api/upload', require('./upload')); - app.post(`/api/delete/:id${idregx}`, require('./delete')); - app.post(`/api/password/:id${idregx}`, require('./password')); - app.post(`/api/params/:id${idregx}`, require('./params')); - app.post(`/api/info/:id${idregx}`, require('./info')); + app.get(`/share/:id${ID_REGEX}`, language, pages.blank); + app.get(`/download/:id${ID_REGEX}`, language, pages.download); + app.get('/completed', language, pages.blank); + app.get('/unsupported/:reason', language, pages.unsupported); + app.get(`/api/download/:id${ID_REGEX}`, auth, require('./download')); + app.get(`/api/exists/:id${ID_REGEX}`, require('./exists')); + app.get(`/api/metadata/:id${ID_REGEX}`, auth, require('./metadata')); + app.post('/api/upload', uploader, require('./upload')); + app.post(`/api/delete/:id${ID_REGEX}`, owner, require('./delete')); + app.post(`/api/password/:id${ID_REGEX}`, owner, require('./password')); + app.post(`/api/params/:id${ID_REGEX}`, owner, require('./params')); + app.post(`/api/info/:id${ID_REGEX}`, owner, require('./info')); app.get('/__version__', function(req, res) { res.sendFile(require.resolve('../../dist/version.json')); diff --git a/server/routes/info.js b/server/routes/info.js index f133c35cb..b993706d0 100644 --- a/server/routes/info.js +++ b/server/routes/info.js @@ -1,21 +1,11 @@ const storage = require('../storage'); module.exports = async function(req, res) { - const id = req.params.id; - const ownerToken = req.body.owner_token; - if (!ownerToken) { - return res.sendStatus(400); - } - try { - const meta = await storage.metadata(id); - if (meta.owner !== ownerToken) { - return res.sendStatus(400); - } - const ttl = await storage.ttl(id); + const ttl = await storage.ttl(req.params.id); return res.send({ - dlimit: +meta.dlimit, - dtotal: +meta.dl, + dlimit: +req.meta.dlimit, + dtotal: +req.meta.dl, ttl }); } catch (e) { diff --git a/server/routes/metadata.js b/server/routes/metadata.js index a99942d2d..a671cee68 100644 --- a/server/routes/metadata.js +++ b/server/routes/metadata.js @@ -1,28 +1,14 @@ const storage = require('../storage'); -const crypto = require('crypto'); module.exports = async function(req, res) { const id = req.params.id; - + const meta = req.meta; try { - const auth = req.header('Authorization').split(' ')[1]; - const meta = await storage.metadata(id); - const hmac = crypto.createHmac('sha256', Buffer.from(meta.auth, 'base64')); - hmac.update(Buffer.from(meta.nonce, 'base64')); - const verifyHash = hmac.digest(); - if (!verifyHash.equals(Buffer.from(auth, 'base64'))) { - res.set('WWW-Authenticate', `send-v1 ${meta.nonce}`); - return res.sendStatus(401); - } - const nonce = crypto.randomBytes(16).toString('base64'); - storage.setField(id, 'nonce', nonce); - res.set('WWW-Authenticate', `send-v1 ${nonce}`); - const size = await storage.length(id); const ttl = await storage.ttl(id); res.send({ metadata: meta.metadata, - finalDownload: +meta.dl + 1 === +meta.dlimit, + finalDownload: meta.dl + 1 === meta.dlimit, size, ttl }); diff --git a/server/routes/params.js b/server/routes/params.js index 90219d933..3cb754604 100644 --- a/server/routes/params.js +++ b/server/routes/params.js @@ -1,23 +1,13 @@ const storage = require('../storage'); -module.exports = async function(req, res) { - const id = req.params.id; - const ownerToken = req.body.owner_token; - if (!ownerToken) { - return res.sendStatus(400); - } - +module.exports = function(req, res) { const dlimit = req.body.dlimit; if (!dlimit || dlimit > 20) { return res.sendStatus(400); } try { - const meta = await storage.metadata(id); - if (meta.owner !== ownerToken) { - return res.sendStatus(400); - } - storage.setField(id, 'dlimit', dlimit); + storage.setField(req.params.id, 'dlimit', dlimit); res.sendStatus(200); } catch (e) { res.sendStatus(404); diff --git a/server/routes/password.js b/server/routes/password.js index 2be6fe9ab..bf2f5679b 100644 --- a/server/routes/password.js +++ b/server/routes/password.js @@ -1,23 +1,15 @@ const storage = require('../storage'); -module.exports = async function(req, res) { +module.exports = function(req, res) { const id = req.params.id; - const ownerToken = req.body.owner_token; - if (!ownerToken) { - return res.sendStatus(404); - } const auth = req.body.auth; if (!auth) { return res.sendStatus(400); } try { - const meta = await storage.metadata(id); - if (meta.owner !== ownerToken) { - return res.sendStatus(404); - } storage.setField(id, 'auth', auth); - storage.setField(id, 'pwd', 1); + storage.setField(id, 'pwd', true); res.sendStatus(200); } catch (e) { return res.sendStatus(404); diff --git a/server/routes/upload.js b/server/routes/upload.js index 6dac96aea..e566bdc8a 100644 --- a/server/routes/upload.js +++ b/server/routes/upload.js @@ -14,12 +14,8 @@ module.exports = function(req, res) { } const owner = crypto.randomBytes(10).toString('hex'); const meta = { - dlimit: 1, - dl: 0, owner, - delete: owner, // delete is deprecated metadata, - pwd: 0, auth: auth.split(' ')[1], nonce: crypto.randomBytes(16).toString('base64') }; @@ -47,7 +43,7 @@ module.exports = function(req, res) { req.on('close', async err => { try { - await storage.forceDelete(newId); + await storage.del(newId); } catch (e) { log.info('DeleteError:', newId); } diff --git a/server/storage.js b/server/storage.js deleted file mode 100644 index 35ec64183..000000000 --- a/server/storage.js +++ /dev/null @@ -1,285 +0,0 @@ -const AWS = require('aws-sdk'); -const s3 = new AWS.S3(); -const mkdirp = require('mkdirp'); - -const config = require('./config'); -const fs = require('fs'); -const path = require('path'); - -const mozlog = require('./log'); - -const log = mozlog('send.storage'); - -const redis_lib = - config.env === 'development' && config.redis_host === 'localhost' - ? 'redis-mock' - : 'redis'; - -const redis = require(redis_lib); -const redis_client = redis.createClient({ - host: config.redis_host, - connect_timeout: 10000 -}); - -redis_client.on('error', err => { - log.error('Redis:', err); -}); - -const fileDir = config.file_dir; - -if (config.s3_bucket) { - module.exports = { - exists: exists, - ttl: ttl, - length: awsLength, - get: awsGet, - set: awsSet, - setField: setField, - delete: awsDelete, - forceDelete: awsForceDelete, - ping: awsPing, - flushall: flushall, - quit: quit, - metadata - }; -} else { - mkdirp.sync(config.file_dir); - log.info('fileDir', fileDir); - module.exports = { - exists: exists, - ttl: ttl, - length: localLength, - get: localGet, - set: localSet, - setField: setField, - delete: localDelete, - forceDelete: localForceDelete, - ping: localPing, - flushall: flushall, - quit: quit, - metadata - }; -} - -if (config.redis_event_expire) { - const forceDelete = config.s3_bucket ? awsForceDelete : localForceDelete; - const redis_sub = redis_client.duplicate(); - const subKey = '__keyevent@0__:expired'; - redis_sub.psubscribe(subKey, function() { - log.info('Redis:', 'subscribed to expired key events'); - }); - - redis_sub.on('pmessage', function(channel, message, id) { - log.info('RedisExpired:', id); - forceDelete(id); - }); -} - -function flushall() { - redis_client.flushdb(); -} - -function quit() { - redis_client.quit(); -} - -function metadata(id) { - return new Promise((resolve, reject) => { - redis_client.hgetall(id, (err, reply) => { - if (err || !reply) { - return reject(err); - } - resolve(reply); - }); - }); -} - -function ttl(id) { - return new Promise((resolve, reject) => { - redis_client.ttl(id, (err, reply) => { - if (err || !reply) { - return reject(err); - } - resolve(reply * 1000); - }); - }); -} - -function exists(id) { - return new Promise((resolve, reject) => { - redis_client.exists(id, (rediserr, reply) => { - if (reply === 1 && !rediserr) { - resolve(); - } else { - reject(rediserr); - } - }); - }); -} - -function setField(id, key, value) { - redis_client.hset(id, key, value); -} - -function localLength(id) { - return new Promise((resolve, reject) => { - try { - resolve(fs.statSync(path.join(fileDir, id)).size); - } catch (err) { - reject(); - } - }); -} - -function localGet(id) { - return fs.createReadStream(path.join(fileDir, id)); -} - -function localSet(newId, file, meta) { - return new Promise((resolve, reject) => { - const filepath = path.join(fileDir, newId); - const fstream = fs.createWriteStream(filepath); - file.pipe(fstream); - file.on('limit', () => { - file.unpipe(fstream); - fstream.destroy(new Error('limit')); - }); - fstream.on('finish', () => { - redis_client.hmset(newId, meta); - redis_client.expire(newId, config.expire_seconds); - log.info('localSet:', 'Upload Finished of ' + newId); - resolve(meta.owner); - }); - - fstream.on('error', err => { - log.error('localSet:', 'Failed upload of ' + newId); - fs.unlinkSync(filepath); - reject(err); - }); - }); -} - -function localDelete(id, ownerToken) { - return new Promise((resolve, reject) => { - redis_client.hget(id, 'delete', (err, reply) => { - if (!reply || ownerToken !== reply) { - reject(); - } else { - redis_client.del(id); - log.info('Deleted:', id); - resolve(fs.unlinkSync(path.join(fileDir, id))); - } - }); - }); -} - -function localForceDelete(id) { - return new Promise((resolve, reject) => { - redis_client.del(id); - resolve(fs.unlinkSync(path.join(fileDir, id))); - }); -} - -function localPing() { - return new Promise((resolve, reject) => { - redis_client.ping(err => { - return err ? reject() : resolve(); - }); - }); -} - -function awsLength(id) { - const params = { - Bucket: config.s3_bucket, - Key: id - }; - return new Promise((resolve, reject) => { - s3.headObject(params, function(err, data) { - if (!err) { - resolve(data.ContentLength); - } else { - reject(); - } - }); - }); -} - -function awsGet(id) { - const params = { - Bucket: config.s3_bucket, - Key: id - }; - - try { - return s3.getObject(params).createReadStream(); - } catch (err) { - return null; - } -} - -function awsSet(newId, file, meta) { - const params = { - Bucket: config.s3_bucket, - Key: newId, - Body: file - }; - let hitLimit = false; - const upload = s3.upload(params); - file.on('limit', () => { - hitLimit = true; - upload.abort(); - }); - return upload.promise().then( - () => { - redis_client.hmset(newId, meta); - redis_client.expire(newId, config.expire_seconds); - }, - err => { - if (hitLimit) { - throw new Error('limit'); - } else { - throw err; - } - } - ); -} - -function awsDelete(id, ownerToken) { - return new Promise((resolve, reject) => { - redis_client.hget(id, 'delete', (err, reply) => { - if (!reply || ownerToken !== reply) { - reject(); - } else { - const params = { - Bucket: config.s3_bucket, - Key: id - }; - - s3.deleteObject(params, function(err, _data) { - redis_client.del(id); - err ? reject(err) : resolve(err); - }); - } - }); - }); -} - -function awsForceDelete(id) { - return new Promise((resolve, reject) => { - const params = { - Bucket: config.s3_bucket, - Key: id - }; - - s3.deleteObject(params, function(err, _data) { - redis_client.del(id); - err ? reject(err) : resolve(); - }); - }); -} - -function awsPing() { - return localPing().then(() => - s3.headBucket({ Bucket: config.s3_bucket }).promise() - ); -} diff --git a/server/storage/fs.js b/server/storage/fs.js new file mode 100644 index 000000000..54fcd53a9 --- /dev/null +++ b/server/storage/fs.js @@ -0,0 +1,50 @@ +const fs = require('fs'); +const path = require('path'); +const promisify = require('util').promisify; +const mkdirp = require('mkdirp'); + +const stat = promisify(fs.stat); + +class FSStorage { + constructor(config, log) { + this.log = log; + this.dir = config.file_dir; + mkdirp.sync(this.dir); + } + + async length(id) { + const result = await stat(path.join(this.dir, id)); + return result.size; + } + + getStream(id) { + return fs.createReadStream(path.join(this.dir, id)); + } + + set(id, file) { + return new Promise((resolve, reject) => { + const filepath = path.join(this.dir, id); + const fstream = fs.createWriteStream(filepath); + file.pipe(fstream); + file.on('limit', () => { + file.unpipe(fstream); + fstream.destroy(new Error('limit')); + }); + fstream.on('error', err => { + fs.unlinkSync(filepath); + reject(err); + }); + fstream.on('finish', resolve); + }); + } + + del(id) { + return Promise.resolve(fs.unlinkSync(path.join(this.dir, id))); + } + + ping() { + return Promise.resolve(); + } +} + +module.exports = FSStorage; diff --git a/server/storage/index.js b/server/storage/index.js new file mode 100644 index 000000000..9a8100c29 --- /dev/null +++ b/server/storage/index.js @@ -0,0 +1,57 @@ +const config = require('../config'); +const Metadata = require('../metadata'); +const mozlog = require('../log'); +const createRedisClient = require('./redis'); + +class DB { + constructor(config) { + const Storage = config.s3_bucket ? require('./s3') : require('./fs'); + this.log = mozlog('send.storage'); + this.expireSeconds = config.expire_seconds; + this.storage = new Storage(config, this.log); + this.redis = createRedisClient(config); + this.redis.on('error', err => { + this.log.error('Redis:', err); + }); + } + + async ttl(id) { + const result = await this.redis.ttlAsync(id); + return Math.ceil(result) * 1000; + } + + length(id) { + return this.storage.length(id); + } + + get(id) { + return this.storage.getStream(id); + } + + async set(id, file, meta) { + await this.storage.set(id, file); + this.redis.hmset(id, meta); + this.redis.expire(id, this.expireSeconds); + } + + setField(id, key, value) { + this.redis.hset(id, key, value); + } + + del(id) { + this.redis.del(id); + return this.storage.del(id); + } + + async ping() { + await this.redis.pingAsync(); + await this.storage.ping(); + } + + async metadata(id) { + const result = await this.redis.hgetallAsync(id); + return result && new Metadata(result); + } +} + +module.exports = new DB(config); diff --git a/server/storage/redis.js b/server/storage/redis.js new file mode 100644 index 000000000..063c977dc --- /dev/null +++ b/server/storage/redis.js @@ -0,0 +1,21 @@ +const promisify = require('util').promisify; + +module.exports = function(config) { + const redis_lib = + config.env === 'development' && config.redis_host === 'localhost' + ? 'redis-mock' + : 'redis'; + + //eslint-disable-next-line security/detect-non-literal-require + const redis = require(redis_lib); + const client = redis.createClient({ + host: config.redis_host, + connect_timeout: 10000 + }); + + client.ttlAsync = promisify(client.ttl); + client.hgetallAsync = promisify(client.hgetall); + client.hgetAsync = promisify(client.hget); + client.pingAsync = promisify(client.ping); + return client; +}; diff --git a/server/storage/s3.js b/server/storage/s3.js new file mode 100644 index 000000000..b77f91b31 --- /dev/null +++ b/server/storage/s3.js @@ -0,0 +1,51 @@ +const AWS = require('aws-sdk'); +const s3 = new AWS.S3(); + +class S3Storage { + constructor(config, log) { + this.bucket = config.s3_bucket; + this.log = log; + } + + async length(id) { + const result = await s3 + .headObject({ Bucket: this.bucket, Key: id }) + .promise(); + return result.ContentLength; + } + + getStream(id) { + return s3.getObject({ Bucket: this.bucket, Key: id }).createReadStream(); + } + + async set(id, file) { + let hitLimit = false; + const upload = s3.upload({ + Bucket: this.bucket, + Key: id, + Body: file + }); + file.on('limit', () => { + hitLimit = true; + upload.abort(); + }); + try { + await upload.promise(); + } catch (e) { + if (hitLimit) { + throw new Error('limit'); + } + throw e; + } + } + + del(id) { + return s3.deleteObject({ Bucket: this.bucket, Key: id }).promise(); + } + + ping() { + return s3.headBucket({ Bucket: this.bucket }).promise(); + } +} + +module.exports = S3Storage; diff --git a/test/unit/auth-tests.js b/test/unit/auth-tests.js new file mode 100644 index 000000000..632007abb --- /dev/null +++ b/test/unit/auth-tests.js @@ -0,0 +1,105 @@ +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + metadata: sinon.stub(), + setField: sinon.stub() +}; + +function request(id, auth) { + return { + params: { id }, + header: sinon.stub().returns(auth) + }; +} + +function response() { + return { + sendStatus: sinon.stub(), + set: sinon.stub() + }; +} + +const next = sinon.stub(); + +const storedMeta = { + auth: + 'r9uFxEs9GEVaQR9CJJ0uTKFGhFSOTRjOY2FCLFlCIZ0Cr-VGTVpMGlXDbNR8RMT55trMpSrzWtBVKq1LffOT2g', + nonce: 'FL4oxA7IE1PW8shwFN9qZw==' +}; + +const authMiddleware = proxyquire('../../server/middleware/auth', { + '../storage': storage +}); + +describe('Owner Middleware', function() { + afterEach(function() { + storage.metadata.reset(); + storage.setField.reset(); + next.reset(); + }); + + it('sends a 401 when no auth header is set', async function() { + const req = request('x'); + const res = response(); + await authMiddleware(req, res, next); + sinon.assert.calledWith(res.sendStatus, 401); + sinon.assert.notCalled(next); + }); + + it('sends a 404 when metadata is not found', async function() { + const req = request('x', 'y'); + const res = response(); + await authMiddleware(req, res, next); + sinon.assert.calledWith(res.sendStatus, 404); + sinon.assert.notCalled(next); + }); + + it('sends a 401 when the auth header is invalid base64', async function() { + storage.metadata.returns(Promise.resolve(storedMeta)); + const req = request('x', '1'); + const res = response(); + await authMiddleware(req, res, next); + sinon.assert.calledWith(res.sendStatus, 401); + sinon.assert.notCalled(next); + }); + + it('authenticates when the hashes match', async function() { + storage.metadata.returns(Promise.resolve(storedMeta)); + const req = request( + 'x', + 'send-v1 R7nZk14qJqZXtxpnAtw2uDIRQTRnO1qSO1Q0PiwcNA8' + ); + const res = response(); + await authMiddleware(req, res, next); + sinon.assert.calledOnce(next); + sinon.assert.calledWith(storage.setField, 'x', 'nonce', req.nonce); + sinon.assert.calledWith( + res.set, + 'WWW-Authenticate', + `send-v1 ${req.nonce}` + ); + sinon.assert.notCalled(res.sendStatus); + assert.equal(req.authorized, true); + assert.equal(req.meta, storedMeta); + assert.notEqual(req.nonce, storedMeta.nonce); + }); + + it('sends a 401 when the hashes do not match', async function() { + storage.metadata.returns(Promise.resolve(storedMeta)); + const req = request( + 'x', + 'send-v1 R8nZk14qJqZXtxpnAtw2uDIRQTRnO1qSO1Q0PiwcNA8' + ); + const res = response(); + await authMiddleware(req, res, next); + sinon.assert.calledWith(res.sendStatus, 401); + sinon.assert.calledWith( + res.set, + 'WWW-Authenticate', + `send-v1 ${storedMeta.nonce}` + ); + sinon.assert.notCalled(next); + }); +}); diff --git a/test/unit/aws.storage.test.js b/test/unit/aws.storage.test.js deleted file mode 100644 index 1505a7e72..000000000 --- a/test/unit/aws.storage.test.js +++ /dev/null @@ -1,173 +0,0 @@ -const assert = require('assert'); -const sinon = require('sinon'); -const proxyquire = require('proxyquire'); -const crypto = require('crypto'); - -const redisStub = {}; -const exists = sinon.stub(); -const hget = sinon.stub(); -const hmset = sinon.stub(); -const expire = sinon.spy(); -const del = sinon.stub(); - -redisStub.createClient = function() { - return { - on: sinon.spy(), - exists: exists, - hget: hget, - hmset: hmset, - expire: expire, - del: del - }; -}; - -const fsStub = {}; -fsStub.statSync = sinon.stub(); -fsStub.createReadStream = sinon.stub(); -fsStub.createWriteStream = sinon.stub(); -fsStub.unlinkSync = sinon.stub(); - -const logStub = {}; -logStub.info = sinon.stub(); -logStub.error = sinon.stub(); - -const s3Stub = {}; -s3Stub.headObject = sinon.stub(); -s3Stub.getObject = sinon.stub(); -s3Stub.upload = sinon.stub(); -s3Stub.deleteObject = sinon.stub(); - -const awsStub = { - S3: function() { - return s3Stub; - } -}; - -const storage = proxyquire('../../server/storage', { - redis: redisStub, - 'redis-mock': redisStub, - fs: fsStub, - './log': function() { - return logStub; - }, - 'aws-sdk': awsStub, - './config': { - s3_bucket: 'test' - } -}); - -describe('Testing Length using aws', function() { - it('Filesize returns properly if id exists', function() { - s3Stub.headObject.callsArgWith(1, null, { ContentLength: 1 }); - return storage - .length('123') - .then(reply => assert.equal(reply, 1)) - .catch(err => assert.fail()); - }); - - it('Filesize fails if the id does not exist', function() { - s3Stub.headObject.callsArgWith(1, new Error(), null); - return storage - .length('123') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Get using aws', function() { - it('Should not error out when the file exists', function() { - const spy = sinon.spy(); - s3Stub.getObject.returns({ - createReadStream: spy - }); - - storage.get('123'); - assert(spy.calledOnce); - }); - - it('Should error when the file does not exist', function() { - const err = function() { - throw new Error(); - }; - const spy = sinon.spy(err); - s3Stub.getObject.returns({ - createReadStream: spy - }); - - assert.equal(storage.get('123'), null); - assert(spy.threw()); - }); -}); - -describe('Testing Set using aws', function() { - beforeEach(function() { - expire.resetHistory(); - }); - - after(function() { - crypto.randomBytes.restore(); - }); - - it('Should pass when the file is successfully uploaded', function() { - const buf = Buffer.alloc(10); - sinon.stub(crypto, 'randomBytes').returns(buf); - s3Stub.upload.returns({ promise: () => Promise.resolve() }); - return storage - .set('123', { on: sinon.stub() }, 'Filename.moz', {}) - .then(() => { - assert(expire.calledOnce); - assert(expire.calledWith('123', 86400)); - }) - .catch(err => assert.fail()); - }); - - it('Should fail if there was an error during uploading', function() { - s3Stub.upload.returns({ promise: () => Promise.reject() }); - return storage - .set('123', { on: sinon.stub() }, 'Filename.moz', 'url.com') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Delete from aws', function() { - it('Returns successfully if the id is deleted off aws', function() { - hget.callsArgWith(2, null, 'delete_token'); - s3Stub.deleteObject.callsArgWith(1, null, {}); - return storage - .delete('file_id', 'delete_token') - .then(_reply => assert(1), err => assert.fail()); - }); - - it('Delete fails if id exists locally but does not in aws', function() { - hget.callsArgWith(2, null, 'delete_token'); - s3Stub.deleteObject.callsArgWith(1, new Error(), {}); - return storage - .delete('file_id', 'delete_token') - .then(_reply => assert.fail(), err => assert(1)); - }); - - it('Delete fails if the delete token does not match', function() { - hget.callsArgWith(2, null, {}); - return storage - .delete('Filename.moz', 'delete_token') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Forced Delete from aws', function() { - it('Deletes properly if id exists', function() { - s3Stub.deleteObject.callsArgWith(1, null, {}); - return storage - .forceDelete('file_id', 'delete_token') - .then(_reply => assert(1), err => assert.fail()); - }); - - it('Deletes fails if id does not exist', function() { - s3Stub.deleteObject.callsArgWith(1, new Error(), {}); - return storage - .forceDelete('file_id') - .then(_reply => assert.fail(), err => assert(1)); - }); -}); diff --git a/test/unit/delete-tests.js b/test/unit/delete-tests.js new file mode 100644 index 000000000..1de32349b --- /dev/null +++ b/test/unit/delete-tests.js @@ -0,0 +1,43 @@ +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + del: sinon.stub() +}; + +function request(id) { + return { + params: { id } + }; +} + +function response() { + return { + sendStatus: sinon.stub() + }; +} + +const delRoute = proxyquire('../../server/routes/delete', { + '../storage': storage +}); + +describe('/api/delete', function() { + afterEach(function() { + storage.del.reset(); + }); + + it('calls storage.del with the id parameter', async function() { + const req = request('x'); + const res = response(); + await delRoute(req, res); + sinon.assert.calledWith(storage.del, 'x'); + sinon.assert.calledWith(res.sendStatus, 200); + }); + + it('sends a 404 on failure', async function() { + storage.del.returns(Promise.reject(new Error())); + const res = response(); + await delRoute(request('x'), res); + sinon.assert.calledWith(res.sendStatus, 404); + }); +}); diff --git a/test/unit/info-tests.js b/test/unit/info-tests.js new file mode 100644 index 000000000..d0a562fac --- /dev/null +++ b/test/unit/info-tests.js @@ -0,0 +1,59 @@ +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + ttl: sinon.stub() +}; + +function request(id, meta) { + return { + params: { id }, + meta + }; +} + +function response() { + return { + sendStatus: sinon.stub(), + send: sinon.stub() + }; +} + +const infoRoute = proxyquire('../../server/routes/info', { + '../storage': storage +}); + +describe('/api/info', function() { + afterEach(function() { + storage.ttl.reset(); + }); + + it('calls storage.ttl with the id parameter', async function() { + const req = request('x'); + const res = response(); + await infoRoute(req, res); + sinon.assert.calledWith(storage.ttl, 'x'); + }); + + it('sends a 404 on failure', async function() { + storage.ttl.returns(Promise.reject(new Error())); + const res = response(); + await infoRoute(request('x'), res); + sinon.assert.calledWith(res.sendStatus, 404); + }); + + it('returns a json object', async function() { + storage.ttl.returns(Promise.resolve(123)); + const meta = { + dlimit: '1', + dl: '0' + }; + const res = response(); + await infoRoute(request('x', meta), res); + sinon.assert.calledWithMatch(res.send, { + dlimit: 1, + dtotal: 0, + ttl: 123 + }); + }); +}); diff --git a/test/unit/language-tests.js b/test/unit/language-tests.js new file mode 100644 index 000000000..1480d15fa --- /dev/null +++ b/test/unit/language-tests.js @@ -0,0 +1,67 @@ +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const config = { + l10n_dev: false // prod configuration +}; +const pkg = { + availableLanguages: ['en-US', 'fr', 'it', 'es-ES'] +}; + +function request(acceptLang) { + return { + headers: { + 'accept-language': acceptLang + } + }; +} + +const langMiddleware = proxyquire('../../server/middleware/language', { + '../config': config, + '../../package.json': pkg +}); + +describe('Language Middleware', function() { + it('defaults to en-US when no header is present', function() { + const req = request(); + const next = sinon.stub(); + langMiddleware(req, null, next); + assert.equal(req.language, 'en-US'); + sinon.assert.calledOnce(next); + }); + + it('sets req.language to en-US when accept-language > 255 chars', function() { + const accept = Array(257).join('a'); + assert.equal(accept.length, 256); + const req = request(accept); + const next = sinon.stub(); + langMiddleware(req, null, next); + assert.equal(req.language, 'en-US'); + sinon.assert.calledOnce(next); + }); + + it('defaults to en-US when no accept-language is available', function() { + const req = request('fa,cs,ja'); + const next = sinon.stub(); + langMiddleware(req, null, next); + assert.equal(req.language, 'en-US'); + sinon.assert.calledOnce(next); + }); + + it('prefers higher q values', function() { + const req = request('fa;q=0.5, it;q=0.9'); + const next = sinon.stub(); + langMiddleware(req, null, next); + assert.equal(req.language, 'it'); + sinon.assert.calledOnce(next); + }); + + it('uses likely subtags', function() { + const req = request('es-MX'); + const next = sinon.stub(); + langMiddleware(req, null, next); + assert.equal(req.language, 'es-ES'); + sinon.assert.calledOn(next); + }); +}); diff --git a/test/unit/local.storage.test.js b/test/unit/local.storage.test.js deleted file mode 100644 index 495c6e8ee..000000000 --- a/test/unit/local.storage.test.js +++ /dev/null @@ -1,163 +0,0 @@ -const assert = require('assert'); -const sinon = require('sinon'); -const proxyquire = require('proxyquire'); - -const redisStub = {}; -const exists = sinon.stub(); -const hget = sinon.stub(); -const hmset = sinon.stub(); -const expire = sinon.stub(); -const del = sinon.stub(); - -redisStub.createClient = function() { - return { - on: sinon.spy(), - exists: exists, - hget: hget, - hmset: hmset, - expire: expire, - del: del - }; -}; - -const fsStub = {}; -fsStub.statSync = sinon.stub(); -fsStub.createReadStream = sinon.stub(); -fsStub.createWriteStream = sinon.stub(); -fsStub.unlinkSync = sinon.stub(); - -const logStub = {}; -logStub.info = sinon.stub(); -logStub.error = sinon.stub(); - -const storage = proxyquire('../../server/storage', { - redis: redisStub, - 'redis-mock': redisStub, - fs: fsStub, - './log': function() { - return logStub; - } -}); - -describe('Testing Exists from local filesystem', function() { - it('Exists returns true when file exists', function() { - exists.callsArgWith(1, null, 1); - return storage - .exists('test') - .then(() => assert(1)) - .catch(err => assert.fail()); - }); - - it('Exists returns false when file does not exist', function() { - exists.callsArgWith(1, null, 0); - return storage - .exists('test') - .then(() => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Length from local filesystem', function() { - it('Filesize returns properly if id exists', function() { - fsStub.statSync.returns({ size: 10 }); - return storage - .length('Filename.moz') - .then(_reply => assert(1)) - .catch(err => assert.fail()); - }); - - it('Filesize fails if the id does not exist', function() { - fsStub.statSync.returns(null); - return storage - .length('Filename.moz') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Get from local filesystem', function() { - it('Get returns properly if id exists', function() { - fsStub.createReadStream.returns(1); - if (storage.get('Filename.moz')) { - assert(1); - } else { - assert.fail(); - } - }); - - it('Get fails if the id does not exist', function() { - fsStub.createReadStream.returns(null); - if (storage.get('Filename.moz')) { - assert.fail(); - } else { - assert(1); - } - }); -}); - -describe('Testing Set to local filesystem', function() { - it('Successfully writes the file to the local filesystem', function() { - const stub = sinon.stub(); - stub.withArgs('finish', sinon.match.any).callsArgWithAsync(1); - stub.withArgs('error', sinon.match.any).returns(1); - fsStub.createWriteStream.returns({ on: stub }); - - return storage - .set('test', { pipe: sinon.stub(), on: sinon.stub() }, 'Filename.moz', {}) - .then(() => { - assert(1); - }) - .catch(err => assert.fail()); - }); - - it('Fails when the file is not properly written to the local filesystem', function() { - const stub = sinon.stub(); - stub.withArgs('error', sinon.match.any).callsArgWithAsync(1); - stub.withArgs('close', sinon.match.any).returns(1); - fsStub.createWriteStream.returns({ on: stub }); - - return storage - .set('test', { pipe: sinon.stub() }, 'Filename.moz', 'moz.la') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Delete from local filesystem', function() { - it('Deletes properly if id exists', function() { - hget.callsArgWith(2, null, '123'); - fsStub.unlinkSync.returns(1); - return storage - .delete('Filename.moz', '123') - .then(reply => assert(reply)) - .catch(err => assert.fail()); - }); - - it('Delete fails if id does not exist', function() { - hget.callsArgWith(2, null, null); - return storage - .delete('Filename.moz', '123') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); - - it('Delete fails if the delete token does not match', function() { - hget.callsArgWith(2, null, null); - return storage - .delete('Filename.moz', '123') - .then(_reply => assert.fail()) - .catch(err => assert(1)); - }); -}); - -describe('Testing Forced Delete from local filesystem', function() { - it('Deletes properly if id exists', function() { - fsStub.unlinkSync.returns(1); - return storage.forceDelete('Filename.moz').then(reply => assert(reply)); - }); - - it('Deletes fails if id does not exist, but no reject is called', function() { - fsStub.unlinkSync.returns(0); - return storage.forceDelete('Filename.moz').then(reply => assert(!reply)); - }); -}); diff --git a/test/unit/metadata-tests.js b/test/unit/metadata-tests.js new file mode 100644 index 000000000..fbaeaefd4 --- /dev/null +++ b/test/unit/metadata-tests.js @@ -0,0 +1,65 @@ +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + ttl: sinon.stub(), + length: sinon.stub() +}; + +function request(id, meta) { + return { + params: { id }, + meta + }; +} + +function response() { + return { + sendStatus: sinon.stub(), + send: sinon.stub() + }; +} + +const metadataRoute = proxyquire('../../server/routes/metadata', { + '../storage': storage +}); + +describe('/api/metadata', function() { + afterEach(function() { + storage.ttl.reset(); + storage.length.reset(); + }); + + it('calls storage.[ttl|length] with the id parameter', async function() { + const req = request('x'); + const res = response(); + await metadataRoute(req, res); + sinon.assert.calledWith(storage.ttl, 'x'); + sinon.assert.calledWith(storage.length, 'x'); + }); + + it('sends a 404 on failure', async function() { + storage.length.returns(Promise.reject(new Error())); + const res = response(); + await metadataRoute(request('x'), res); + sinon.assert.calledWith(res.sendStatus, 404); + }); + + it('returns a json object', async function() { + storage.ttl.returns(Promise.resolve(123)); + storage.length.returns(Promise.resolve(987)); + const meta = { + dlimit: 1, + dl: 0, + metadata: 'foo' + }; + const res = response(); + await metadataRoute(request('x', meta), res); + sinon.assert.calledWithMatch(res.send, { + metadata: 'foo', + finalDownload: true, + size: 987, + ttl: 123 + }); + }); +}); diff --git a/test/unit/owner-tests.js b/test/unit/owner-tests.js new file mode 100644 index 000000000..a5dd65899 --- /dev/null +++ b/test/unit/owner-tests.js @@ -0,0 +1,81 @@ +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + metadata: sinon.stub() +}; + +function request(id, owner_token) { + return { + params: { id }, + body: { owner_token } + }; +} + +function response() { + return { + sendStatus: sinon.stub() + }; +} + +const ownerMiddleware = proxyquire('../../server/middleware/owner', { + '../storage': storage +}); + +describe('Owner Middleware', function() { + afterEach(function() { + storage.metadata.reset(); + }); + + it('sends a 404 when the id is not found', async function() { + const next = sinon.stub(); + storage.metadata.returns(Promise.resolve(null)); + const res = response(); + await ownerMiddleware(request('x', 'y'), res); + sinon.assert.notCalled(next); + sinon.assert.calledWith(res.sendStatus, 404); + }); + + it('sends a 401 when the owner_token is missing', async function() { + const next = sinon.stub(); + const meta = { owner: 'y' }; + storage.metadata.returns(Promise.resolve(meta)); + const res = response(); + await ownerMiddleware(request('x', null), res); + sinon.assert.notCalled(next); + sinon.assert.calledWith(res.sendStatus, 401); + }); + + it('sends a 401 when the owner_token does not match', async function() { + const next = sinon.stub(); + const meta = { owner: 'y' }; + storage.metadata.returns(Promise.resolve(meta)); + const res = response(); + await ownerMiddleware(request('x', 'z'), res); + sinon.assert.notCalled(next); + sinon.assert.calledWith(res.sendStatus, 401); + }); + + it('sends a 401 if the metadata call fails', async function() { + const next = sinon.stub(); + storage.metadata.returns(Promise.reject(new Error())); + const res = response(); + await ownerMiddleware(request('x', 'y'), res); + sinon.assert.notCalled(next); + sinon.assert.calledWith(res.sendStatus, 401); + }); + + it('sets req.meta and req.authorized on successful auth', async function() { + const next = sinon.stub(); + const meta = { owner: 'y' }; + storage.metadata.returns(Promise.resolve(meta)); + const req = request('x', 'y'); + const res = response(); + await ownerMiddleware(req, res, next); + assert.equal(req.meta, meta); + assert.equal(req.authorized, true); + sinon.assert.notCalled(res.sendStatus); + sinon.assert.calledOnce(next); + }); +}); diff --git a/test/unit/params-tests.js b/test/unit/params-tests.js new file mode 100644 index 000000000..cba749f9c --- /dev/null +++ b/test/unit/params-tests.js @@ -0,0 +1,56 @@ +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + setField: sinon.stub() +}; + +function request(id) { + return { + params: { id }, + body: {} + }; +} + +function response() { + return { + sendStatus: sinon.stub() + }; +} + +const paramsRoute = proxyquire('../../server/routes/params', { + '../storage': storage +}); + +describe('/api/params', function() { + afterEach(function() { + storage.setField.reset(); + }); + + it('calls storage.setField with the correct parameter', function() { + const req = request('x'); + const dlimit = 2; + req.body.dlimit = dlimit; + const res = response(); + paramsRoute(req, res); + sinon.assert.calledWith(storage.setField, 'x', 'dlimit', dlimit); + sinon.assert.calledWith(res.sendStatus, 200); + }); + + it('sends a 400 if dlimit is too large', function() { + const req = request('x'); + const res = response(); + req.body.dlimit = 21; + paramsRoute(req, res); + sinon.assert.calledWith(res.sendStatus, 400); + }); + + it('sends a 404 on failure', function() { + storage.setField.throws(new Error()); + const req = request('x'); + const res = response(); + req.body.dlimit = 2; + paramsRoute(req, res); + sinon.assert.calledWith(res.sendStatus, 404); + }); +}); diff --git a/test/unit/password-tests.js b/test/unit/password-tests.js new file mode 100644 index 000000000..03c76c4c8 --- /dev/null +++ b/test/unit/password-tests.js @@ -0,0 +1,53 @@ +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const storage = { + setField: sinon.stub() +}; + +function request(id, body) { + return { + params: { id }, + body + }; +} + +function response() { + return { + sendStatus: sinon.stub() + }; +} + +const passwordRoute = proxyquire('../../server/routes/password', { + '../storage': storage +}); + +describe('/api/password', function() { + afterEach(function() { + storage.setField.reset(); + }); + + it('calls storage.setField with the correct parameter', function() { + const req = request('x', { auth: 'z' }); + const res = response(); + passwordRoute(req, res); + sinon.assert.calledWith(storage.setField, 'x', 'auth', 'z'); + sinon.assert.calledWith(storage.setField, 'x', 'pwd', true); + sinon.assert.calledWith(res.sendStatus, 200); + }); + + it('sends a 400 if auth is missing', function() { + const req = request('x', {}); + const res = response(); + passwordRoute(req, res); + sinon.assert.calledWith(res.sendStatus, 400); + }); + + it('sends a 404 on failure', function() { + storage.setField.throws(new Error()); + const req = request('x', { auth: 'z' }); + const res = response(); + passwordRoute(req, res); + sinon.assert.calledWith(res.sendStatus, 404); + }); +}); diff --git a/test/unit/s3-tests.js b/test/unit/s3-tests.js new file mode 100644 index 000000000..68e55f368 --- /dev/null +++ b/test/unit/s3-tests.js @@ -0,0 +1,154 @@ +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +function resolvedPromise(val) { + return { + promise: () => Promise.resolve(val) + }; +} + +function rejectedPromise(err) { + return { + promise: () => Promise.reject(err) + }; +} + +const s3Stub = { + headObject: sinon.stub(), + getObject: sinon.stub(), + upload: sinon.stub(), + deleteObject: sinon.stub() +}; + +const awsStub = { + S3: function() { + return s3Stub; + } +}; + +const S3Storage = proxyquire('../../server/storage/s3', { + 'aws-sdk': awsStub +}); + +describe('S3Storage', function() { + it('uses config.s3_bucket', function() { + const s = new S3Storage({ s3_bucket: 'foo' }); + assert.equal(s.bucket, 'foo'); + }); + + describe('length', function() { + it('returns the ContentLength', async function() { + s3Stub.headObject = sinon + .stub() + .returns(resolvedPromise({ ContentLength: 123 })); + const s = new S3Storage({ s3_bucket: 'foo' }); + const len = await s.length('x'); + assert.equal(len, 123); + sinon.assert.calledWithMatch(s3Stub.headObject, { + Bucket: 'foo', + Key: 'x' + }); + }); + + it('throws when id not found', async function() { + const err = new Error(); + s3Stub.headObject = sinon.stub().returns(rejectedPromise(err)); + const s = new S3Storage({ s3_bucket: 'foo' }); + try { + await s.length('x'); + assert.fail(); + } catch (e) { + assert.equal(e, err); + } + }); + }); + + describe('getStream', function() { + it('returns a Stream object', function() { + const stream = {}; + s3Stub.getObject = sinon + .stub() + .returns({ createReadStream: () => stream }); + const s = new S3Storage({ s3_bucket: 'foo' }); + const result = s.getStream('x'); + assert.equal(result, stream); + sinon.assert.calledWithMatch(s3Stub.getObject, { + Bucket: 'foo', + Key: 'x' + }); + }); + }); + + describe('set', function() { + it('calls s3.upload', async function() { + const file = { on: sinon.stub() }; + s3Stub.upload = sinon.stub().returns(resolvedPromise()); + const s = new S3Storage({ s3_bucket: 'foo' }); + await s.set('x', file); + sinon.assert.calledWithMatch(s3Stub.upload, { + Bucket: 'foo', + Key: 'x', + Body: file + }); + }); + + it('aborts upload if limit is hit', async function() { + const file = { + on: (ev, fn) => fn() + }; + const abort = sinon.stub(); + const err = new Error(); + s3Stub.upload = sinon.stub().returns({ + promise: () => Promise.reject(err), + abort + }); + const s = new S3Storage({ s3_bucket: 'foo' }); + try { + await s.set('x', file); + assert.fail(); + } catch (e) { + assert.equal(e.message, 'limit'); + sinon.assert.calledOnce(abort); + } + }); + + it('throws when s3.upload fails', async function() { + const file = { + on: sinon.stub() + }; + const err = new Error(); + s3Stub.upload = sinon.stub().returns(rejectedPromise(err)); + const s = new S3Storage({ s3_bucket: 'foo' }); + try { + await s.set('x', file); + assert.fail(); + } catch (e) { + assert.equal(e, err); + } + }); + }); + + describe('del', function() { + it('calls s3.deleteObject', async function() { + s3Stub.deleteObject = sinon.stub().returns(resolvedPromise(true)); + const s = new S3Storage({ s3_bucket: 'foo' }); + const result = await s.del('x'); + assert.equal(result, true); + sinon.assert.calledWithMatch(s3Stub.deleteObject, { + Bucket: 'foo', + Key: 'x' + }); + }); + }); + + describe('ping', function() { + it('calls s3.headBucket', async function() { + s3Stub.headBucket = sinon.stub().returns(resolvedPromise(true)); + const s = new S3Storage({ s3_bucket: 'foo' }); + const result = await s.ping(); + assert.equal(result, true); + sinon.assert.calledWithMatch(s3Stub.headBucket, { Bucket: 'foo' }); + }); + }); +}); diff --git a/test/unit/storage-tests.js b/test/unit/storage-tests.js new file mode 100644 index 000000000..18137fb8d --- /dev/null +++ b/test/unit/storage-tests.js @@ -0,0 +1,118 @@ +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const stream = {}; +class MockStorage { + length() { + return Promise.resolve(12); + } + getStream() { + return stream; + } + set() { + return Promise.resolve(); + } + del() { + return Promise.resolve(); + } + ping() { + return Promise.resolve(); + } +} + +const expire_seconds = 10; +const storage = proxyquire('../../server/storage', { + '../config': { + expire_seconds, + s3_bucket: 'foo', + env: 'development', + redis_host: 'localhost' + }, + '../log': () => {}, + './s3': MockStorage +}); + +describe('Storage', function() { + describe('ttl', function() { + it('returns milliseconds remaining', async function() { + await storage.set('x', null, { foo: 'bar' }); + const ms = await storage.ttl('x'); + await storage.del('x'); + assert.equal(ms, expire_seconds * 1000); + }); + }); + + describe('length', function() { + it('returns the file size', async function() { + const len = await storage.length('x'); + assert.equal(len, 12); + }); + }); + + describe('get', function() { + it('returns a stream', function() { + const s = storage.get('x'); + assert.equal(s, stream); + }); + }); + + describe('set', function() { + it('sets expiration to config.expire_seconds', async function() { + await storage.set('x', null, { foo: 'bar' }); + const s = await storage.redis.ttlAsync('x'); + await storage.del('x'); + assert.equal(Math.ceil(s), expire_seconds); + }); + + it('sets metadata', async function() { + const m = { foo: 'bar' }; + await storage.set('x', null, m); + const meta = await storage.redis.hgetallAsync('x'); + await storage.del('x'); + assert.deepEqual(meta, m); + }); + + //it('throws when storage fails'); + }); + + describe('setField', function() { + it('works', async function() { + storage.setField('x', 'y', 'z'); + const z = await storage.redis.hgetAsync('x', 'y'); + assert.equal(z, 'z'); + await storage.del('x'); + }); + }); + + describe('del', function() { + it('works', async function() { + await storage.set('x', null, { foo: 'bar' }); + await storage.del('x'); + const meta = await storage.metadata('x'); + assert.equal(meta, null); + }); + }); + + describe('ping', function() { + it('works', async function() { + await storage.ping(); + }); + }); + + describe('metadata', function() { + it('returns all metadata fields', async function() { + const m = { + pwd: true, + dl: 1, + dlimit: 1, + auth: 'foo', + metadata: 'bar', + nonce: 'baz', + owner: 'bmo' + }; + await storage.set('x', null, m); + const meta = await storage.metadata('x'); + assert.deepEqual(meta, m); + }); + }); +}); From 950c9cdaeb7a64181ca3bafdb68abd5387ecda77 Mon Sep 17 00:00:00 2001 From: Jim Spentzos Date: Sat, 10 Feb 2018 20:32:06 +0000 Subject: [PATCH 0012/1871] Pontoon: Update Greek (el) localization of Test Pilot: Firefox Send Localization authors: - Jim Spentzos --- public/locales/el/send.ftl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/public/locales/el/send.ftl b/public/locales/el/send.ftl index 51040d0fb..6541d6b73 100644 --- a/public/locales/el/send.ftl +++ b/public/locales/el/send.ftl @@ -41,6 +41,12 @@ deleteFileButton = Διαγραφή αρχείου sendAnotherFileLink = Αποστολή άλλου αρχείου // Alternative text used on the download link/button (indicates an action). downloadAltText = Λήψη +downloadsFileList = Λήψεις +// Used as header in a column indicating the amount of time left before a +// download link expires (e.g. "10h 5m") +timeFileList = Ώρα +// Used as header in a column indicating the number of times a file has been +// downloaded downloadFileName = Λήψη του { $filename } downloadFileSize = ({ $size }) unlockInputLabel = Εισαγωγή κωδικού πρόσβασης @@ -95,7 +101,15 @@ footerLinkTerms = Όροι footerLinkCookies = Cookies requirePasswordCheckbox = Απαίτηση κωδικού πρόσβασης για λήψη του αρχείου addPasswordButton = Προσθήκη κωδικού πρόσβασης +changePasswordButton = Αλλαγή passwordTryAgain = Λάθος κωδικός πρόσβασης. Δοκιμάστε ξανά. // This label is followed by the password needed to download a file passwordResult = Κωδικός πρόσβασης: { $password } reportIPInfringement = Αναφορά παραβίασης IP +javascriptRequired = Το Firefox Send απαιτεί JavaScript +whyJavascript = Γιατί το Firefox Send απαιτεί JavaScript; +enableJavascript = Παρακαλώ ενεργοποιήστε το JavaScript και δοκιμάστε ξανά. +// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" +expiresHoursMinutes = { $hours }ώ { $minutes }λ +// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" +expiresMinutes = { $minutes }λ From dcfda9521ba7a05d739081a678fa8d0da2e24220 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Sun, 11 Feb 2018 14:02:40 -0800 Subject: [PATCH 0013/1871] nit: use 'html/raw' instead of html() where possible --- app/pages/legal.js | 7 ++++--- app/pages/share.js | 9 +++++---- app/templates/uploadPasswordSet.js | 9 +++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/pages/legal.js b/app/pages/legal.js index af196ab9a..3b92b4d1c 100644 --- a/app/pages/legal.js +++ b/app/pages/legal.js @@ -1,4 +1,5 @@ const html = require('choo/html'); +const raw = require('choo/html/raw'); function replaceLinks(str, urls) { let i = -1; @@ -6,7 +7,7 @@ function replaceLinks(str, urls) { i++; return `${v}`; }); - return [`
    ${s}
    `]; + return `
    ${s}
    `; } module.exports = function(state) { @@ -14,14 +15,14 @@ module.exports = function(state) {
    `; + if (!(div instanceof String)) { + setTimeout(() => document.querySelector('#unlock-input').focus()); + } + function inputChanged() { const input = document.getElementById('unlock-input'); const btn = document.getElementById('unlock-btn'); From 43698f8d61ad9f9c31958f80eaf160944fb7e9bb Mon Sep 17 00:00:00 2001 From: Merike Sell Date: Mon, 12 Feb 2018 18:10:42 +0000 Subject: [PATCH 0016/1871] Pontoon: Update Estonian (et) localization of Test Pilot: Firefox Send Localization authors: - Merike Sell --- public/locales/et/send.ftl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/locales/et/send.ftl b/public/locales/et/send.ftl index 836dd8777..9d6e6dfe9 100644 --- a/public/locales/et/send.ftl +++ b/public/locales/et/send.ftl @@ -25,6 +25,15 @@ uploadingFileNotification = Teavita mind üleslaadimise lõppemisest. uploadSuccessConfirmHeader = Saatmiseks valmis uploadSvgAlt = Laadi üles uploadSuccessTimingHeader = Link failile aegub pärast 1. allalaadimist või 24 tunni möödumisel. +expireInfo = Link failile aegub peale { $downloadCount } või { $timespan }. +downloadCount = { $num -> + [one] üht allalaadimist + *[other] { $num } allalaadimist + } +timespanHours = { $num -> + [one] üht tundi + *[other] { $num } tundi + } copyUrlFormLabelWithName = Kopeeri ja jaga linki faili allalaadimiseks: { $filename } copyUrlFormButton = Kopeeri vahemällu copiedUrl = Kopeeritud! @@ -32,6 +41,12 @@ deleteFileButton = Kustuta fail sendAnotherFileLink = Saada järgmine fail // Alternative text used on the download link/button (indicates an action). downloadAltText = Laadi alla +downloadsFileList = Allalaadimised +// Used as header in a column indicating the amount of time left before a +// download link expires (e.g. "10h 5m") +timeFileList = Aega jäänud +// Used as header in a column indicating the number of times a file has been +// downloaded downloadFileName = Laadi fail { $filename } alla downloadFileSize = ({ $size }) unlockInputLabel = Sisesta parool @@ -86,6 +101,15 @@ footerLinkTerms = Teenusetingimused footerLinkCookies = Küpsistest requirePasswordCheckbox = Selle faili allalaadimiseks nõutakse parooli addPasswordButton = Lisa parool +changePasswordButton = Muuda passwordTryAgain = Vale parool. Palun proovi uuesti. // This label is followed by the password needed to download a file passwordResult = Parool: { $password } +reportIPInfringement = Intellektuaalomandi keelatud kasutamise raporteerimine +javascriptRequired = Firefox Send'i kasutamiseks tuleb JavaScript lubada +whyJavascript = Miks Firefox Send JavaScripti vajab? +enableJavascript = Palun luba JavaScript ja proovi uuesti. +// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" +expiresHoursMinutes = { $hours }t { $minutes }m +// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" +expiresMinutes = { $minutes }m From 29c36ee110394452f42ff081b8472a110c2976d4 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Mon, 12 Feb 2018 11:35:33 -0800 Subject: [PATCH 0017/1871] updated deps --- package-lock.json | 1063 ++++++++++++++++++++++++++++++--------------- package.json | 25 +- 2 files changed, 724 insertions(+), 364 deletions(-) diff --git a/package-lock.json b/package-lock.json index fccae2b86..ecd18ac1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,15 @@ "integrity": "sha1-9vGlzl05caSt6RoR0i1MRZrNN18=", "dev": true }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, "JSONStream": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", @@ -157,6 +166,12 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "any-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", + "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=", + "dev": true + }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", @@ -365,13 +380,13 @@ "dev": true }, "autoprefixer": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.5.tgz", - "integrity": "sha512-XqHfo8Ht0VU+T5P+eWEVoXza456KJ4l62BPewu3vpNf3LP9s2+zYXkXBznzYby4XeECXgG3N4i+hGvOhXErZmA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.0.0.tgz", + "integrity": "sha512-XBEqAoESCyGu3daYmWcTC37Dwmjvs0y40UtUO3MMX+Pd/w7jwNFfUKNtxoMFu0u0wcotP+arDpU3JVH54UV79Q==", "dev": true, "requires": { - "browserslist": "2.11.3", - "caniuse-lite": "1.0.30000792", + "browserslist": "3.0.0", + "caniuse-lite": "1.0.30000808", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "6.0.17", @@ -388,47 +403,42 @@ } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.0.0.tgz", + "integrity": "sha512-5ArwxNIJxmBTUUTe+F7P2AM8wNf6zFa9mb3/o0JCrhGdd042PuVgAZg4M+A29NUpqFEYl+H3kQQYoZUXfuRS9g==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000792", - "electron-to-chromium": "1.3.32" + "caniuse-lite": "1.0.30000808", + "electron-to-chromium": "1.3.33" } }, "caniuse-lite": { - "version": "1.0.30000792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz", - "integrity": "sha1-0M6pgfgRjzlhRxr7tDyaHlu/AzI=", + "version": "1.0.30000808", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz", + "integrity": "sha512-vT0JLmHdvq1UVbYXioxCXHYdNw55tyvi+IUWyX0Zeh1OFQi2IllYtm38IJnSgHWCv/zUnX1hdhy3vMJvuTNSqw==", "dev": true }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - }, - "dependencies": { - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } + "supports-color": "5.2.0" } }, "electron-to-chromium": { - "version": "1.3.32", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.32.tgz", - "integrity": "sha1-EdBoTAhA4APEvoko+KxfNdvCtOY=", + "version": "1.3.33", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz", + "integrity": "sha1-vwBwPWKnxlI4E2V4w1LWxcBCpUU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "postcss": { @@ -437,26 +447,26 @@ "integrity": "sha512-Bl1nybsSzWYbP8O4gAVD8JIjZIul9hLNOPTGBIlVmZNUnNAGL+W0cpYWzVwfImZOwumct4c1SDvSbncVWKtXUw==", "dev": true, "requires": { - "chalk": "2.3.0", + "chalk": "2.3.1", "source-map": "0.6.1", - "supports-color": "5.1.0" + "supports-color": "5.2.0" } }, "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } }, "aws-sdk": { - "version": "2.189.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.189.0.tgz", - "integrity": "sha1-TfZJuM+iAI/uCfNX/jqYNUZcTaQ=", + "version": "2.192.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.192.0.tgz", + "integrity": "sha1-QXDZCGvR8H51F8xHutN9EmduxSo=", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -1523,7 +1533,7 @@ "dev": true, "requires": { "bn.js": "4.11.8", - "randombytes": "2.0.5" + "randombytes": "2.0.6" } }, "browserify-sign": { @@ -1624,7 +1634,7 @@ "move-concurrently": "1.0.1", "promise-inflight": "1.0.1", "rimraf": "2.6.2", - "ssri": "5.1.0", + "ssri": "5.2.1", "unique-filename": "1.1.0", "y18n": "3.2.1" } @@ -2326,9 +2336,9 @@ } }, "copy-webpack-plugin": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.3.1.tgz", - "integrity": "sha512-xlcFiW/U7KrpS6dFuWq3r8Wb7koJx7QVc7LDFCosqkikaVSxkaYOnwDLwilbjrszZ0LYZXThDAJKcQCSrvdShQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.4.1.tgz", + "integrity": "sha512-ojaz8MpS3zoLJT/JbYMusYM+dCEArhW24hGAUPYPydTCS+87NFh2TWr85sywG3So4Q4E68QoerqQ+Ns1g0fhDg==", "dev": true, "requires": { "cacache": "10.0.2", @@ -2336,10 +2346,8 @@ "globby": "7.1.1", "is-glob": "4.0.0", "loader-utils": "0.2.17", - "lodash": "4.17.4", "minimatch": "3.0.4", "p-limit": "1.1.0", - "pify": "3.0.0", "serialize-javascript": "1.4.0" }, "dependencies": { @@ -2383,32 +2391,42 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz", - "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "js-yaml": "3.7.0", - "minimist": "1.2.0", - "object-assign": "4.1.1", - "os-homedir": "1.0.2", - "parse-json": "2.2.0", - "pinkie-promise": "2.0.1", - "require-from-string": "1.2.1" + "is-directory": "0.3.1", + "js-yaml": "3.10.0", + "parse-json": "4.0.0", + "require-from-string": "2.0.1" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", "dev": true }, - "require-from-string": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", - "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", - "dev": true + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "1.3.1", + "json-parse-better-errors": "1.0.1" + } } } }, @@ -2431,7 +2449,7 @@ "cipher-base": "1.0.4", "inherits": "2.0.3", "ripemd160": "2.0.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "create-hmac": { @@ -2445,7 +2463,7 @@ "inherits": "2.0.3", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "cross-env": { @@ -2483,6 +2501,25 @@ "boom": "0.4.2" } }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "1.0.0", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.0", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "diffie-hellman": "5.0.2", + "inherits": "2.0.3", + "pbkdf2": "3.0.14", + "public-encrypt": "4.0.0", + "randombytes": "2.0.6", + "randomfill": "1.0.3" + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -2847,7 +2884,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "dasherize": { @@ -2886,6 +2923,22 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "1.2.0", + "map-obj": "1.0.1" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -2988,9 +3041,9 @@ } }, "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", + "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", "dev": true }, "diffie-hellman": { @@ -3001,7 +3054,7 @@ "requires": { "bn.js": "4.11.8", "miller-rabin": "4.0.1", - "randombytes": "2.0.5" + "randombytes": "2.0.6" } }, "dir-glob": { @@ -3139,9 +3192,9 @@ "dev": true }, "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "domelementtype": { @@ -3160,9 +3213,9 @@ } }, "domutils": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", - "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "dev": true, "requires": { "dom-serializer": "0.1.0", @@ -3211,20 +3264,26 @@ "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.4", "stream-shift": "1.0.0" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -3354,9 +3413,9 @@ } }, "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", "dev": true, "requires": { "es6-iterator": "2.0.3", @@ -3370,7 +3429,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-symbol": "3.1.1" } }, @@ -3381,7 +3440,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-set": "0.1.5", "es6-symbol": "3.1.1", @@ -3418,7 +3477,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", "event-emitter": "0.3.5" @@ -3431,7 +3490,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "es6-templates": { @@ -3451,7 +3510,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } @@ -3638,23 +3697,15 @@ } }, "eslint-plugin-node": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz", - "integrity": "sha512-xhPXrh0Vl/b7870uEbaumb2Q+LxaEcOQ3kS1jtIXanBAwpMre1l5q/l2l/hESYJGEFKuI78bp6Uw50hlpr7B+g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-6.0.0.tgz", + "integrity": "sha512-Qj4dMF1N/wRALO1IRvnchn8c1i0awgrztrGx7MjF9ewDwlW/heNB+WeZ09bhp8Yp0TD+BZcADP8BRya0wmropA==", "dev": true, "requires": { "ignore": "3.3.7", "minimatch": "3.0.4", "resolve": "1.5.0", - "semver": "5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - } + "semver": "5.4.1" } }, "eslint-plugin-security": { @@ -3758,7 +3809,7 @@ "dev": true, "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "event-stream": { @@ -4108,6 +4159,12 @@ "pkg-dir": "2.0.0" } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -4161,19 +4218,25 @@ "dev": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -4236,15 +4299,6 @@ } } }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.3.0" - } - }, "formidable": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", @@ -4279,19 +4333,25 @@ "dev": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -5464,6 +5524,23 @@ "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", "dev": true }, + "gonzales-pe": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.3.tgz", + "integrity": "sha512-Kjhohco0esHQnOiqqdJeNz/5fyPkOMD/d6XVjwTAoPGUFh0mCollPUTUTa2OZy4dYNAqlPIQdTiNzJTWdd9Htw==", + "dev": true, + "requires": { + "minimist": "1.1.3" + }, + "dependencies": { + "minimist": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", + "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -5543,9 +5620,9 @@ "dev": true }, "helmet": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.10.0.tgz", - "integrity": "sha512-wVu5jSeImztLqNQPc4hqGr1DG0Ki2UJVmQ1KTugIrtl1f4Zw5SqVqh6QPyw5b6/Jo/iAnyTt+pcehB0RdEJsbw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.11.0.tgz", + "integrity": "sha512-Xqf6VXmjpZoyH4reGyeBCO5nHH0NVeRQnx23LFj6AK9ocPRgZJfSH6zZ8SvNO2tB+fKhsqy1RSjIjWHVvH1X+w==", "requires": { "dns-prefetch-control": "0.1.0", "dont-sniff-mimetype": "1.0.0", @@ -5714,22 +5791,28 @@ "requires": { "domelementtype": "1.3.0", "domhandler": "2.4.1", - "domutils": "1.6.2", + "domutils": "1.7.0", "entities": "1.1.1", "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -6305,6 +6388,15 @@ "integrity": "sha1-AO+8CIFsM8/ErIJR0TLhDcZQmNc=", "dev": true }, + "is-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", + "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", + "dev": true, + "requires": { + "symbol-observable": "0.2.4" + } + }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -6463,7 +6555,7 @@ "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==", "dev": true, "requires": { - "chalk": "2.3.0", + "chalk": "2.3.1", "jest-get-type": "21.2.0", "leven": "2.1.0", "pretty-format": "21.2.1" @@ -6479,23 +6571,29 @@ } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -6721,9 +6819,9 @@ } }, "known-css-properties": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.4.1.tgz", - "integrity": "sha512-n+ThoCKhyMFKkMfksdLMP5ndp+VzwDRzQdH6JlmZ2GTpUenYB2EeEKjOue2SErAAG/MmBSUISpwvawDhydWQdQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.5.0.tgz", + "integrity": "sha512-LOS0CoS8zcZnB1EjLw4LLqDXw8nvt3AGH5dXLQP3D9O1nLLA+9GC5GnPl5mmF+JiQAtSX4VyZC7KvEtcA4kUtA==", "dev": true }, "lazy-cache": { @@ -6807,26 +6905,31 @@ } }, "lint-staged": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.3.0.tgz", - "integrity": "sha512-C/Zxslg0VRbsxwmCu977iIs+QyrmW2cyRCPUV5NDFYOH/jtRFHH8ch7ua2fH0voI/nVC3Tpg7DykfgMZySliKw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-6.1.0.tgz", + "integrity": "sha512-RMB6BUd2bEKaPnj06F7j8RRB8OHM+UP4fQS2LT8lF+X9BjSaezw1oVB5hc4elLhYvzlFCkhAaatzYz+x53YHgw==", "dev": true, "requires": { "app-root-path": "2.0.1", - "chalk": "2.3.0", + "chalk": "2.3.1", "commander": "2.12.2", - "cosmiconfig": "1.1.0", + "cosmiconfig": "4.0.0", + "debug": "3.1.0", + "dedent": "0.7.0", "execa": "0.8.0", + "find-parent-dir": "0.3.0", "is-glob": "4.0.0", "jest-validate": "21.2.1", - "listr": "0.12.0", + "listr": "0.13.0", "lodash": "4.17.4", - "log-symbols": "2.1.0", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "npm-which": "3.0.1", "p-map": "1.2.0", + "path-is-inside": "1.0.2", + "pify": "3.0.0", "staged-git-files": "0.0.4", - "stringify-object": "3.2.1" + "stringify-object": "3.2.2" }, "dependencies": { "ansi-styles": { @@ -6839,48 +6942,64 @@ } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } }, "listr": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", - "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.13.0.tgz", + "integrity": "sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=", "dev": true, "requires": { "chalk": "1.1.3", "cli-truncate": "0.2.1", "figures": "1.7.0", "indent-string": "2.1.0", + "is-observable": "0.2.0", "is-promise": "2.1.0", "is-stream": "1.1.0", "listr-silent-renderer": "1.1.1", - "listr-update-renderer": "0.2.0", + "listr-update-renderer": "0.4.0", "listr-verbose-renderer": "0.4.1", "log-symbols": "1.0.2", "log-update": "1.0.2", "ora": "0.2.3", "p-map": "1.2.0", - "rxjs": "5.5.2", - "stream-to-observable": "0.1.0", + "rxjs": "5.5.6", + "stream-to-observable": "0.2.0", "strip-ansi": "3.0.1" }, "dependencies": { @@ -6912,9 +7031,9 @@ "dev": true }, "listr-update-renderer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", - "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz", + "integrity": "sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=", "dev": true, "requires": { "chalk": "1.1.3", @@ -7094,12 +7213,12 @@ "dev": true }, "log-symbols": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz", - "integrity": "sha512-zLeLrzMA1A2vRF1e/0Mo+LNINzi6jzBylHj5WqvQ/WK/5WCZt8si9SyN4p9llr/HRYvVR1AoXHRHl4WTHyQAzQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.3.0" + "chalk": "2.3.1" }, "dependencies": { "ansi-styles": { @@ -7112,23 +7231,29 @@ } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -7235,11 +7360,6 @@ "yallist": "2.1.2" } }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, "macaddress": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", @@ -7330,7 +7450,7 @@ "dev": true, "requires": { "unist-util-modify-children": "1.1.1", - "unist-util-visit": "1.2.0" + "unist-util-visit": "1.3.0" } }, "media-typer": { @@ -7625,6 +7745,16 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "is-plain-obj": "1.1.0" + } + }, "mississippi": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-1.3.1.tgz", @@ -7869,14 +7999,14 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "nise": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.2.tgz", - "integrity": "sha512-rvxf+PSZeCKtP0DgmwMmNf1G3I6X1r4WHiP2H88PlIkOkt7mGqufdokjS8caoHBgZzVx0ee/5ytGcGHbZaUw8w==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.5.tgz", + "integrity": "sha512-Es4hGuq3lpip5PckrB+Qpuma282M0UJANJ+jxAgI+0wWTL9X6MtNv+M385JgqsAE8hv6NvD3lv8CQtXgEnvlpQ==", "dev": true, "requires": { - "formatio": "1.2.0", + "@sinonjs/formatio": "2.0.0", "just-extend": "1.1.27", - "lolex": "1.6.0", + "lolex": "2.3.2", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" }, @@ -7887,12 +8017,6 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", - "dev": true - }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -7936,7 +8060,7 @@ "console-browserify": "1.1.0", "constants-browserify": "1.0.0", "crypto-browserify": "3.12.0", - "domain-browser": "1.1.7", + "domain-browser": "1.2.0", "events": "1.1.1", "https-browserify": "1.0.0", "os-browserify": "0.3.0", @@ -7944,52 +8068,39 @@ "process": "0.11.10", "punycode": "1.3.2", "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", + "readable-stream": "2.3.4", "stream-browserify": "2.0.1", - "stream-http": "2.7.2", + "stream-http": "2.8.0", "string_decoder": "1.0.3", - "timers-browserify": "2.0.4", + "timers-browserify": "2.0.6", "tty-browserify": "0.0.0", "url": "0.11.0", "util": "0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.5", - "randomfill": "1.0.3" - } - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -8089,9 +8200,9 @@ } }, "npm-path": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz", - "integrity": "sha1-Fc/04ciaONp39W9gVbJPl137K74=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", "dev": true, "requires": { "which": "1.3.0" @@ -8161,7 +8272,7 @@ "dev": true, "requires": { "commander": "2.12.2", - "npm-path": "2.0.3", + "npm-path": "2.0.4", "which": "1.3.0" } }, @@ -10086,19 +10197,25 @@ "requires": { "cyclist": "0.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -10295,7 +10412,7 @@ "create-hmac": "1.1.6", "ripemd160": "2.0.1", "safe-buffer": "5.1.1", - "sha.js": "2.4.9" + "sha.js": "2.4.10" } }, "pelo": { @@ -10809,9 +10926,9 @@ } }, "postcss-html": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.11.0.tgz", - "integrity": "sha512-ruCTbRZWY+qOV4FNYNm6E0ucIbCkkuYHIqQ4W3iSVIc1aUVBTKMG0iUo2nPUAG2lhFaTmfBaZ17017osZ18ddA==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.12.0.tgz", + "integrity": "sha512-KxKUpj7AY7nlCbLcTOYxdfJnGE7QFAfU2n95ADj1Q90RM/pOLdz8k3n4avOyRFs7MDQHcRzJQWM1dehCwJxisQ==", "dev": true, "requires": { "htmlparser2": "3.9.2", @@ -11667,9 +11784,9 @@ "integrity": "sha512-rBkDbaHAu5uywbCR2XE8a25tats3xSOsGNx6mppK6Q9kSFGKc/FyAzfci+fWM2l+K402p1D0pNcfDGxeje5IKg==", "dev": true, "requires": { - "chalk": "2.3.0", + "chalk": "2.3.1", "lodash": "4.17.4", - "log-symbols": "2.1.0", + "log-symbols": "2.2.0", "postcss": "6.0.14" }, "dependencies": { @@ -11683,23 +11800,29 @@ } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -11719,15 +11842,73 @@ "postcss": "6.0.14" } }, - "postcss-scss": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.2.tgz", - "integrity": "sha1-/0XPM1S4ee6JpOtoaA9GrJuxT5Q=", + "postcss-sass": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.2.0.tgz", + "integrity": "sha512-cUmYzkP747fPCQE6d+CH2l1L4VSyIlAzZsok3HPjb5Gzsq3jE+VjpAdGlPsnQ310WKWI42sw+ar0UNN59/f3hg==", "dev": true, "requires": { + "gonzales-pe": "4.2.3", "postcss": "6.0.14" } }, + "postcss-scss": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.3.tgz", + "integrity": "sha512-N2ZPDOV5PGEGVwdiB7b1QppxKkmkHodNWkemja7PV+/mHqbUlA6ZcYRreden5Ag5nwBBX8/aRE7lfg1xjdszyg==", + "dev": true, + "requires": { + "postcss": "6.0.17" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "5.2.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "6.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.17.tgz", + "integrity": "sha512-Bl1nybsSzWYbP8O4gAVD8JIjZIul9hLNOPTGBIlVmZNUnNAGL+W0cpYWzVwfImZOwumct4c1SDvSbncVWKtXUw==", + "dev": true, + "requires": { + "chalk": "2.3.1", + "source-map": "0.6.1", + "supports-color": "5.2.0" + } + }, + "supports-color": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, "postcss-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", @@ -12024,7 +12205,7 @@ "browserify-rsa": "4.0.1", "create-hash": "1.1.3", "parse-asn1": "5.1.0", - "randombytes": "2.0.5" + "randombytes": "2.0.6" } }, "pump": { @@ -12103,6 +12284,12 @@ "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, "ramda": { "version": "0.24.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", @@ -12151,9 +12338,9 @@ } }, "randombytes": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", - "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -12165,7 +12352,7 @@ "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", "dev": true, "requires": { - "randombytes": "2.0.5", + "randombytes": "2.0.6", "safe-buffer": "5.1.1" } }, @@ -12175,12 +12362,11 @@ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raven": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raven/-/raven-2.4.0.tgz", - "integrity": "sha1-SbfV+DjliT8x3XL4LQWjXkIgP2A=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-2.4.1.tgz", + "integrity": "sha512-LLLS8bOJC1q33qszBsLaEtEg7X8G8hYLGcKO4s6EifAce2BN6cTRdBXNvwVNv4kNk82YUZYrj53yEbL4kCmjjw==", "requires": { "cookie": "0.3.1", - "lsmod": "1.0.0", "md5": "2.2.1", "stack-trace": "0.0.9", "timed-out": "4.0.1", @@ -12200,9 +12386,9 @@ } }, "raven-js": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.22.1.tgz", - "integrity": "sha512-2Y8czUl5a9usbvXbpV8a+GPAiDXjxQjaHImZL0TyJWI5k5jV/6o+wceaBAg9g6RpO9OOJp0/w2mMs6pBoqOyDA==", + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.22.2.tgz", + "integrity": "sha512-UwQzRX/HCU9jpLPNSz+oH5zz+kIKTFzVCSAclV2h/3boGhbP7TocsawN3vbR7wJYV1MPqZkYWgwCedbXvj/iEg==", "dev": true }, "raw-body": { @@ -12813,12 +12999,20 @@ } }, "rxjs": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", - "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", + "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", "dev": true, "requires": { - "symbol-observable": "1.1.0" + "symbol-observable": "1.0.1" + }, + "dependencies": { + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + } } }, "safe-buffer": { @@ -12987,9 +13181,9 @@ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" }, "sha.js": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", + "integrity": "sha512-vnwmrFDlOExK4Nm16J2KMWHLrp14lBrjxMxBJpu++EnsuBmpiYaM/MEs46Vxxm/4FvdP5yTwuCTO9it5FSjrqA==", "dev": true, "requires": { "inherits": "2.0.3", @@ -13041,27 +13235,33 @@ "dev": true }, "sinon": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.2.tgz", - "integrity": "sha512-BEa593xl+IkIc94nKo0O0LauQC/gQy8Gyv4DkzPwF/9DweC5phr1y+42zibCpn9abfkdHxt9r8AhD0R6u9DE/Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.3.0.tgz", + "integrity": "sha512-pmf05hFgEZUS52AGJcsVjOjqAyJW2yo14cOwVYvzCyw7+inv06YXkLyW75WG6X6p951lzkoKh51L2sNbR9CDvw==", "dev": true, "requires": { - "diff": "3.2.0", - "formatio": "1.2.0", + "@sinonjs/formatio": "2.0.0", + "diff": "3.4.0", "lodash.get": "4.4.2", "lolex": "2.3.2", - "nise": "1.2.2", - "supports-color": "5.1.0", + "nise": "1.2.5", + "supports-color": "5.2.0", "type-detect": "4.0.8" }, "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } } } @@ -13316,9 +13516,9 @@ "dev": true }, "ssri": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.1.0.tgz", - "integrity": "sha512-TevC8fgxQKTfQ1nWtM9GNzr3q5rrHNntG9CDMH1k3QhSZI6Kb+NbjLRs8oPFZa2Hgo7zoekL+UTvoEk7tsbjQg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.2.1.tgz", + "integrity": "sha512-y4PjOWlAuxt+yAcXitQYOnOzZpKaH3+f/qGV3OWxbyC2noC9FA9GNC9uILnVdV7jruA1aDKr4OKz3ZDBcVZwFQ==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -13353,19 +13553,25 @@ "dev": true, "requires": { "inherits": "2.0.3", - "readable-stream": "2.3.3" + "readable-stream": "2.3.4" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -13402,28 +13608,34 @@ } }, "stream-http": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.0.tgz", + "integrity": "sha512-sZOFxI/5xw058XIRHl4dU3dZ+TTOIGJR78Dvo0oEAejIt4ou27k+3ne1zYmCV+v7UucbxIFQuOgnkTVHh8YPnw==", "dev": true, "requires": { "builtin-status-codes": "3.0.0", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.4", "to-arraybuffer": "1.0.1", "xtend": "4.0.1" }, "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", "string_decoder": "1.0.3", "util-deprecate": "1.0.2" @@ -13447,10 +13659,13 @@ "dev": true }, "stream-to-observable": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", - "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", - "dev": true + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", + "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", + "dev": true, + "requires": { + "any-observable": "0.2.0" + } }, "streamsearch": { "version": "0.1.2", @@ -13530,9 +13745,9 @@ } }, "stringify-object": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz", - "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.2.tgz", + "integrity": "sha512-O696NF21oLiDy8PhpWu8AEqoZHw++QW6mUv0UvKZe8gWSdSvMXkiLufK7OmnP27Dro4GU5kb9U7JIO0mBuCRQg==", "dev": true, "requires": { "get-own-enumerable-property-symbols": "2.0.1", @@ -13579,14 +13794,14 @@ "dev": true }, "stylelint": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.3.1.tgz", - "integrity": "sha512-v5K6tv+Ky3SoJfNqGOvgzRDLqZV133CZ7Wtu3y5aAGjQHVi+6dixFLgI82VrJZJdC4HwZplafJcRP+4r7EUt5g==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.4.0.tgz", + "integrity": "sha512-56hPH5mTFnk8LzlEuTWq0epa34fHuS54UFYQidBOFt563RJBNi1nz1F2HK2MoT1X1waq47milvRsRahFCCJs/Q==", "dev": true, "requires": { - "autoprefixer": "7.2.5", + "autoprefixer": "7.2.6", "balanced-match": "1.0.0", - "chalk": "2.3.0", + "chalk": "2.3.1", "cosmiconfig": "3.1.0", "debug": "3.1.0", "execall": "1.0.0", @@ -13597,22 +13812,23 @@ "html-tags": "2.0.0", "ignore": "3.3.7", "imurmurhash": "0.1.4", - "known-css-properties": "0.4.1", + "known-css-properties": "0.5.0", "lodash": "4.17.4", - "log-symbols": "2.1.0", + "log-symbols": "2.2.0", "mathml-tag-names": "2.0.1", - "meow": "3.7.0", + "meow": "4.0.0", "micromatch": "2.3.11", "normalize-selector": "0.2.0", "pify": "3.0.0", "postcss": "6.0.14", - "postcss-html": "0.11.0", + "postcss-html": "0.12.0", "postcss-less": "1.1.3", "postcss-media-query-parser": "0.2.3", "postcss-reporter": "5.0.0", "postcss-resolve-nested-selector": "0.1.1", "postcss-safe-parser": "3.0.1", - "postcss-scss": "1.0.2", + "postcss-sass": "0.2.0", + "postcss-scss": "1.0.3", "postcss-selector-parser": "3.1.1", "postcss-value-parser": "3.3.0", "resolve-from": "4.0.0", @@ -13633,15 +13849,69 @@ "color-convert": "1.9.1" } }, + "autoprefixer": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", + "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "dev": true, + "requires": { + "browserslist": "2.11.3", + "caniuse-lite": "1.0.30000808", + "normalize-range": "0.1.2", + "num2fraction": "1.2.2", + "postcss": "6.0.17", + "postcss-value-parser": "3.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.17.tgz", + "integrity": "sha512-Bl1nybsSzWYbP8O4gAVD8JIjZIul9hLNOPTGBIlVmZNUnNAGL+W0cpYWzVwfImZOwumct4c1SDvSbncVWKtXUw==", + "dev": true, + "requires": { + "chalk": "2.3.1", + "source-map": "0.6.1", + "supports-color": "5.2.0" + } + } + } + }, + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "1.0.30000808", + "electron-to-chromium": "1.3.33" + } + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "map-obj": "2.0.0", + "quick-lru": "1.1.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000808", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000808.tgz", + "integrity": "sha512-vT0JLmHdvq1UVbYXioxCXHYdNw55tyvi+IUWyX0Zeh1OFQi2IllYtm38IJnSgHWCv/zUnX1hdhy3vMJvuTNSqw==", + "dev": true + }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.2.0" } }, "cosmiconfig": { @@ -13665,6 +13935,12 @@ "ms": "2.0.0" } }, + "electron-to-chromium": { + "version": "1.3.33", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.33.tgz", + "integrity": "sha1-vwBwPWKnxlI4E2V4w1LWxcBCpUU=", + "dev": true + }, "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", @@ -13685,6 +13961,18 @@ "slash": "1.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "js-yaml": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", @@ -13695,6 +13983,35 @@ "esprima": "4.0.0" } }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.0.tgz", + "integrity": "sha512-Me/kel335m6vMKmEmA6c87Z6DUFW3JqkINRnxkbC+A/PUm0D5Fl2dEBQrPKnqCL9Te/CIa1MUt/0InMJhuC/sw==", + "dev": true, + "requires": { + "camelcase-keys": "4.2.0", + "decamelize-keys": "1.1.0", + "loud-rejection": "1.6.0", + "minimist": "1.2.0", + "minimist-options": "3.0.2", + "normalize-package-data": "2.4.0", + "read-pkg-up": "3.0.0", + "redent": "2.0.0", + "trim-newlines": "2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, "parse-json": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-3.0.0.tgz", @@ -13715,6 +14032,26 @@ "uniq": "1.0.1" } }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "3.2.0", + "strip-indent": "2.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -13722,41 +14059,46 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true } } }, "stylelint-config-recommended": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-1.0.0.tgz", - "integrity": "sha512-wp50rY5A6MWndIIkKNNzJv/S58lTvqQEriS7CXTBN1SwtoY/YjHhCLIOkjundLnUWMvJJska6GnciLbs76UQrA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.0.1.tgz", + "integrity": "sha512-FXdgdOEGpaFQoKGhsi8IbsCI6dkxHQPa1CCqIybkN1d8LKtdxrn/A1rgu8DpJ6J+/4L30FOJeVdPttGfxCDHBQ==", "dev": true }, "stylelint-config-standard": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-17.0.0.tgz", - "integrity": "sha512-G8jMZ0KsaVH7leur9XLZVhwOBHZ2vdbuJV8Bgy0ta7/PpBhEHo6fjVDaNchyCGXB5sRcWVq6O9rEU/MvY9cQDQ==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-18.0.0.tgz", + "integrity": "sha1-DYcrQPr9zdz0GI+1tk3bOIforvw=", "dev": true, "requires": { - "stylelint-config-recommended": "1.0.0" + "stylelint-config-recommended": "2.0.1" } }, "stylelint-no-unsupported-browser-features": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stylelint-no-unsupported-browser-features/-/stylelint-no-unsupported-browser-features-1.0.1.tgz", - "integrity": "sha512-6uaoXV/WA5BLKo9bbjERFE3oAOA0UY4FgGDaQWarV9x3qrDLS2o2SJqk0TaxwAIAgROwj9RhbQ2FF1QKRzZBNw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stylelint-no-unsupported-browser-features/-/stylelint-no-unsupported-browser-features-2.0.0.tgz", + "integrity": "sha512-42U8HyvjgGfA/4Nlez58ffyc5y91344SseGzK5zejaqUWmqNeKGGeKzl8kVizLjxEvtEKItuoZocEzwzDKBb1Q==", "dev": true, "requires": { "doiuse": "4.0.0", "lodash": "4.17.4", - "postcss": "6.0.14", - "stylelint": "8.3.1" + "postcss": "6.0.14" } }, "sugarss": { @@ -13889,9 +14231,9 @@ "integrity": "sha1-tvmpANSWpX8CQI8iGYwQndoGMEE=" }, "symbol-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", - "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", + "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", "dev": true }, "table": { @@ -14022,9 +14364,9 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "timers-browserify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", - "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", + "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", "dev": true, "requires": { "setimmediate": "1.0.5" @@ -14336,7 +14678,7 @@ "integrity": "sha1-WoXBVV/BugwQG4ZwfRXlD6TIcbs=", "dev": true, "requires": { - "unist-util-visit": "1.2.0" + "unist-util-visit": "1.3.0" } }, "unist-util-stringify-position": { @@ -14346,9 +14688,9 @@ "dev": true }, "unist-util-visit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.2.0.tgz", - "integrity": "sha512-lI+jyPlDztHZ2CJhUchcRMQ7MNc0yASgYFxwRTxs0EZ+9HbYFBLVGDJ2FchTBy+pra0O1LVEn0Wkgf19mDVDzw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.3.0.tgz", + "integrity": "sha512-9ntYcxPFtl44gnwXrQKZ5bMqXMY0ZHzUpqMFiU4zcc8mmf/jzYm8GhYgezuUlX4cJIM1zIDYaO6fG/fI+L6iiQ==", "dev": true, "requires": { "unist-util-is": "2.1.1" @@ -14551,15 +14893,15 @@ } }, "webpack": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", - "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.11.0.tgz", + "integrity": "sha512-3kOFejWqj5ISpJk4Qj/V7w98h9Vl52wak3CLiw/cDOfbVTq7FeoZ0SdoHHY9PYlHr50ZS42OfvzE2vB4nncKQg==", "dev": true, "requires": { "acorn": "5.2.1", "acorn-dynamic-import": "2.0.2", - "ajv": "5.5.0", - "ajv-keywords": "2.1.1", + "ajv": "6.1.1", + "ajv-keywords": "3.1.0", "async": "2.6.0", "enhanced-resolve": "3.4.1", "escope": "3.6.0", @@ -14580,6 +14922,23 @@ "yargs": "8.0.2" }, "dependencies": { + "ajv": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.1.1.tgz", + "integrity": "sha1-l41Zf7wrfQ5aXD3esUmmgvKr+g4=", + "dev": true, + "requires": { + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", + "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", + "dev": true + }, "async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", diff --git a/package.json b/package.json index 4b1f35946..a35a910d8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "node": ">=8.2.0" }, "devDependencies": { - "autoprefixer": "^7.2.5", + "autoprefixer": "^8.0.0", "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-plugin-yo-yoify": "^1.0.2", @@ -60,14 +60,14 @@ "babel-preset-es2015": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "base64-js": "^1.2.1", - "copy-webpack-plugin": "^4.3.1", + "copy-webpack-plugin": "^4.4.1", "cross-env": "^5.1.3", "css-loader": "^0.28.9", "css-mqpacker": "^6.0.2", "cssnano": "^3.10.0", "eslint": "^4.17.0", "eslint-plugin-mocha": "^4.11.0", - "eslint-plugin-node": "^5.2.1", + "eslint-plugin-node": "^6.0.0", "eslint-plugin-security": "^1.4.0", "expose-loader": "^0.7.4", "extract-loader": "^1.0.2", @@ -77,7 +77,7 @@ "github-changes": "^1.1.2", "html-loader": "^0.5.5", "husky": "^0.14.3", - "lint-staged": "^4.3.0", + "lint-staged": "^6.1.0", "mocha": "^5.0.0", "nanobus": "^4.3.2", "npm-run-all": "^4.1.2", @@ -86,25 +86,26 @@ "postcss-loader": "^2.1.0", "prettier": "^1.10.2", "proxyquire": "^1.8.0", - "raven-js": "^3.22.1", + "raven-js": "^3.22.2", "redis-mock": "^0.21.0", "require-from-string": "^2.0.1", "rimraf": "^2.6.2", "selenium-webdriver": "^3.6.0", - "sinon": "^4.2.2", + "sinon": "^4.3.0", "string-hash": "^1.1.3", - "stylelint-config-standard": "^17.0.0", - "stylelint-no-unsupported-browser-features": "^1.0.1", + "stylelint": "^8.4.0", + "stylelint-config-standard": "^18.0.0", + "stylelint-no-unsupported-browser-features": "^2.0.0", "supertest": "^3.0.0", "testpilot-ga": "^0.3.0", "val-loader": "^1.1.0", - "webpack": "^3.10.0", + "webpack": "^3.11.0", "webpack-dev-server": "2.9.1", "webpack-manifest-plugin": "^1.3.2", "webpack-unassert-loader": "^1.2.0" }, "dependencies": { - "aws-sdk": "^2.189.0", + "aws-sdk": "^2.192.0", "body-parser": "^1.18.2", "choo": "^6.7.0", "cldr-core": "^32.0.0", @@ -113,10 +114,10 @@ "express": "^4.16.2", "fluent": "^0.4.3", "fluent-langneg": "^0.1.0", - "helmet": "^3.10.0", + "helmet": "^3.11.0", "mkdirp": "^0.5.1", "mozlog": "^2.2.0", - "raven": "^2.4.0", + "raven": "^2.4.1", "redis": "^2.8.0" }, "availableLanguages": [ From a82688163e917ffbeccc0c909b0e41d2fc7ab968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20I?= Date: Wed, 14 Feb 2018 11:11:09 +0000 Subject: [PATCH 0018/1871] Pontoon: Update Norwegian Nynorsk (nn-NO) localization of Test Pilot: Firefox Send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localization authors: - Bjørn I. --- public/locales/nn-NO/send.ftl | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/public/locales/nn-NO/send.ftl b/public/locales/nn-NO/send.ftl index 1737f5cae..7a572088e 100644 --- a/public/locales/nn-NO/send.ftl +++ b/public/locales/nn-NO/send.ftl @@ -1,4 +1,4 @@ -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. title = Firefox Send siteSubtitle = netteksperiment siteFeedback = Tilbakemelding @@ -39,29 +39,29 @@ copyUrlFormButton = Kopier til utklippstavla copiedUrl = Kopiert! deleteFileButton = Slett fil sendAnotherFileLink = Send ei anna fil -// Alternative text used on the download link/button (indicates an action). +# Alternative text used on the download link/button (indicates an action). downloadAltText = Last ned downloadsFileList = Nedlastingar -// Used as header in a column indicating the amount of time left before a -// download link expires (e.g. "10h 5m") +# Used as header in a column indicating the amount of time left before a +# download link expires (e.g. "10h 5m") timeFileList = Tid -// Used as header in a column indicating the number of times a file has been -// downloaded +# Used as header in a column indicating the number of times a file has been +# downloaded downloadFileName = Last ned { $filename } downloadFileSize = ({ $size }) unlockInputLabel = Skriv inn passord unlockInputPlaceholder = Passord unlockButtonLabel = Lås opp downloadFileTitle = Last ned kryptert fil -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. downloadMessage = Vennen din sender deg eni fil med Firefox Send, ei teneste som lar deg dele filer med ei sikker, privat og kryptert lenke, som automatisk går ut, for å sikre at ting ikkje vert verande på nettet for alltid. -// Text and title used on the download link/button (indicates an action). +# Text and title used on the download link/button (indicates an action). downloadButtonLabel = Last ned downloadNotification = Nedlastinga er fullført. downloadFinish = Nedlastinga er fullført. -// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". +# This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". fileSizeProgress = ({ $partialSize } av { $totalSize }) -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. sendYourFilesLink = Prøv Firefox Send downloadingPageProgress = Lastar ned { $filename } ({ $size }) downloadingPageMessage = La denne fana vere open mens vi hentar filen og dekrypterar henne. @@ -73,7 +73,7 @@ fileTooBig = Fila er for stor, og kan ikkje lastast opp. Ho må vere mindre enn linkExpiredAlt = Lenka har gått ut expiredPageHeader = Denne lenka har gått ut eller har aldri eksistert i utgangspunktet! notSupportedHeader = Nettlesaren din er ikkje støtta. -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. notSupportedDetail = Diverre støttar denne nettlesaren ikkje webteknologien som driv Firefox Send. Du må prøve ein annan nettleser. Vi tilrår Firefox! notSupportedLink = Kvifor er ikkje nettlesaren min støtta? notSupportedOutdatedDetail = Dessverre støttar ikkje denne versjonen av Firefox nett-teknologien som driv Firefox Send. Du må å oppdatere nettlesaren din. @@ -81,7 +81,7 @@ updateFirefox = Oppdater Firefox downloadFirefoxButtonSub = Gratis nedlasting uploadedFile = Fil copyFileList = Kopier URL -// expiryFileList is used as a column header +# expiryFileList is used as a column header expiryFileList = Går ut om deleteFileList = Slett nevermindButton = Gløym det @@ -94,7 +94,7 @@ deletePopupCancel = Avbryt deleteButtonHover = Slett copyUrlHover = Kopier URL footerLinkLegal = Juridisk informasjon -// Test Pilot is a proper name and should not be localized. +# Test Pilot is a proper name and should not be localized. footerLinkAbout = Om Test Pilot footerLinkPrivacy = Personvern footerLinkTerms = Vilkår @@ -103,6 +103,13 @@ requirePasswordCheckbox = Krev eit passord for å laste ned denne fila addPasswordButton = Legg til passord changePasswordButton = Endre passwordTryAgain = Feil passord. Prøv på nytt. -// This label is followed by the password needed to download a file +# This label is followed by the password needed to download a file passwordResult = Passord: { $password } reportIPInfringement = Rapporter brot på åndsverk +javascriptRequired = Firefox Send krev JavaScript. +whyJavascript = Kvifor krev Firefox Send JavaScript? +enableJavascript = Slå på JavaScript og prøv igjen. +# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" +expiresHoursMinutes = { $hours }t { $minutes }m +# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" +expiresMinutes = { $minutes }m From c0ad7635f2864533226bb13a74e0cc0962b64b53 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 14 Feb 2018 09:29:37 -0800 Subject: [PATCH 0019/1871] change saveFile. attempting to fix for iOS --- app/utils.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/utils.js b/app/utils.js index e6d8d27b5..b51906b1e 100644 --- a/app/utils.js +++ b/app/utils.js @@ -142,19 +142,29 @@ function fadeOut(id) { } function saveFile(file) { + const reader = new FileReader(); const dataView = new DataView(file.plaintext); const blob = new Blob([dataView], { type: file.type }); - const downloadUrl = URL.createObjectURL(blob); if (window.navigator.msSaveBlob) { return window.navigator.msSaveBlob(blob, file.name); } - const a = document.createElement('a'); - a.href = downloadUrl; - a.download = file.name; - document.body.appendChild(a); - a.click(); - URL.revokeObjectURL(downloadUrl); + reader.addEventListener('loadend', function() { + if (reader.result) { + const a = document.createElement('a'); + a.href = reader.result; + a.download = file.name; + document.body.appendChild(a); + a.click(); + return; + } + if (reader.error) { + console.error(reader.error); + window.location.href = '/error'; + //TODO + } + }); + reader.readAsDataURL(blob); } function openLinksInNewTab(links, should = true) { From b89546ac224f919920dd02da861aa77bc7f377e4 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Wed, 14 Feb 2018 10:47:09 -0800 Subject: [PATCH 0020/1871] ignore stylelint until refactor-css is merged --- .eslintignore | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 7540c39c5..6e3c49041 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ dist assets firefox +coverage diff --git a/package.json b/package.json index a35a910d8..32aab3c90 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "clean": "rimraf dist", "build": "npm run clean && webpack -p", "lint": "npm-run-all lint:*", - "lint:css": "stylelint 'assets/*.css'", + "lint:css": "stylelint -v", "lint:js": "eslint .", "lint-locales": "node scripts/lint-locales", "lint-locales:dev": "npm run lint-locales", From 4f2179c4d7baf0874d99f83b5dcc2b30bda33ded Mon Sep 17 00:00:00 2001 From: "Francesco Lodolo [:flod]" Date: Thu, 15 Feb 2018 11:24:21 +0100 Subject: [PATCH 0021/1871] Switch en-US FTL file to new syntax --- public/locales/en-US/send.ftl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/public/locales/en-US/send.ftl b/public/locales/en-US/send.ftl index 049c498e3..93af284b2 100644 --- a/public/locales/en-US/send.ftl +++ b/public/locales/en-US/send.ftl @@ -1,4 +1,4 @@ -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. title = Firefox Send siteSubtitle = web experiment siteFeedback = Feedback @@ -39,29 +39,29 @@ copyUrlFormButton = Copy to clipboard copiedUrl = Copied! deleteFileButton = Delete file sendAnotherFileLink = Send another file -// Alternative text used on the download link/button (indicates an action). +# Alternative text used on the download link/button (indicates an action). downloadAltText = Download downloadsFileList = Downloads -// Used as header in a column indicating the amount of time left before a -// download link expires (e.g. "10h 5m") +# Used as header in a column indicating the amount of time left before a +# download link expires (e.g. "10h 5m") timeFileList = Time -// Used as header in a column indicating the number of times a file has been -// downloaded +# Used as header in a column indicating the number of times a file has been +# downloaded downloadFileName = Download { $filename } downloadFileSize = ({ $size }) unlockInputLabel = Enter Password unlockInputPlaceholder = Password unlockButtonLabel = Unlock downloadFileTitle = Download Encrypted File -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. downloadMessage = Your friend is sending you a file with Firefox Send, a service that allows you to share files with a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever. -// Text and title used on the download link/button (indicates an action). +# Text and title used on the download link/button (indicates an action). downloadButtonLabel = Download downloadNotification = Your download has completed. downloadFinish = Download Complete -// This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". +# This message is displayed when uploading or downloading a file, e.g. "(1,3 MB of 10 MB)". fileSizeProgress = ({ $partialSize } of { $totalSize }) -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. sendYourFilesLink = Try Firefox Send downloadingPageProgress = Downloading { $filename } ({ $size }) downloadingPageMessage = Please leave this tab open while we fetch your file and decrypt it. @@ -73,7 +73,7 @@ fileTooBig = That file is too big to upload. It should be less than { $size }. linkExpiredAlt = Link expired expiredPageHeader = This link has expired or never existed in the first place! notSupportedHeader = Your browser is not supported. -// Firefox Send is a brand name and should not be localized. +# Firefox Send is a brand name and should not be localized. notSupportedDetail = Unfortunately this browser does not support the web technology that powers Firefox Send. You’ll need to try another browser. We recommend Firefox! notSupportedLink = Why is my browser not supported? notSupportedOutdatedDetail = Unfortunately this version of Firefox does not support the web technology that powers Firefox Send. You’ll need to update your browser. @@ -81,7 +81,7 @@ updateFirefox = Update Firefox downloadFirefoxButtonSub = Free Download uploadedFile = File copyFileList = Copy URL -// expiryFileList is used as a column header +# expiryFileList is used as a column header expiryFileList = Expires In deleteFileList = Delete nevermindButton = Never mind @@ -94,7 +94,7 @@ deletePopupCancel = Cancel deleteButtonHover = Delete copyUrlHover = Copy URL footerLinkLegal = Legal -// Test Pilot is a proper name and should not be localized. +# Test Pilot is a proper name and should not be localized. footerLinkAbout = About Test Pilot footerLinkPrivacy = Privacy footerLinkTerms = Terms @@ -103,13 +103,13 @@ requirePasswordCheckbox = Require a password to download this file addPasswordButton = Add password changePasswordButton = Change passwordTryAgain = Incorrect password. Try again. -// This label is followed by the password needed to download a file +# This label is followed by the password needed to download a file passwordResult = Password: { $password } reportIPInfringement = Report IP Infringement javascriptRequired = Firefox Send requires JavaScript whyJavascript = Why does Firefox Send require JavaScript? enableJavascript = Please enable JavaScript and try again. -// A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" +# A short representation of a countdown timer containing the number of hours and minutes remaining as digits, example "13h 47m" expiresHoursMinutes = { $hours }h { $minutes }m -// A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" -expiresMinutes = { $minutes }m \ No newline at end of file +# A short representation of a countdown timer containing the number of minutes remaining as digits, example "56m" +expiresMinutes = { $minutes }m From 3163edcbe428ff98302e4c2a0610e8266b2d8a10 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Thu, 15 Feb 2018 11:49:41 -0800 Subject: [PATCH 0022/1871] exclude fxios from fxPromo banner. fixes #753 --- app/experiments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/experiments.js b/app/experiments.js index 0b27b3581..90f3b521b 100644 --- a/app/experiments.js +++ b/app/experiments.js @@ -18,7 +18,7 @@ const experiments = { }, eligible: function() { return ( - !/firefox/i.test(navigator.userAgent) && + !/firefox|fxios/i.test(navigator.userAgent) && document.querySelector('html').lang === 'en-US' ); }, From 8d41111cd6b2b9be83474825cf4df4be7d2f8d72 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Tue, 13 Feb 2018 11:32:59 -0800 Subject: [PATCH 0023/1871] refactored css, including some markup changes --- app/base.css | 257 + app/fileManager.js | 4 +- app/main.css | 19 + app/pages/completed.js | 34 - app/pages/completed/index.js | 28 + app/pages/download.js | 46 - app/pages/download/index.js | 43 + app/pages/error/error.css | 11 + app/pages/{error.js => error/index.js} | 6 +- app/pages/legal.js | 32 +- app/pages/{notFound.js => notFound/index.js} | 14 +- app/pages/notFound/notFound.css | 17 + app/pages/preview.js | 44 - app/pages/preview/index.js | 42 + app/pages/preview/preview.css | 4 + app/pages/{share.js => share/index.js} | 93 +- app/pages/share/share.css | 109 + app/pages/unsupported.js | 52 - app/pages/unsupported/index.js | 68 + app/pages/unsupported/unsupported.css | 49 + app/pages/upload.js | 41 - app/pages/upload/index.js | 40 + app/pages/{welcome.js => welcome/index.js} | 38 +- app/pages/welcome/welcome.css | 71 + .../changePasswordSection.css | 29 + app/templates/changePasswordSection/index.js | 52 + .../downloadButton/downloadButton.css | 10 + .../index.js} | 3 +- .../downloadPassword/downloadPassword.css | 30 + .../index.js} | 34 +- app/templates/file/file.css | 26 + app/templates/{file.js => file/index.js} | 51 +- app/templates/fileList/fileList.css | 52 + .../{fileList.js => fileList/index.js} | 24 +- app/templates/footer.js | 43 - app/templates/footer/footer.css | 93 + app/templates/footer/index.js | 64 + app/templates/fxPromo/fxPromo.css | 56 + .../{fxPromo.js => fxPromo/index.js} | 10 +- app/templates/header/header.css | 104 + app/templates/{header.js => header/index.js} | 14 +- app/templates/passwordInput/index.js | 34 + app/templates/passwordInput/passwordInput.css | 18 + app/templates/popup/index.js | 26 + app/templates/popup/popup.css | 122 + .../{progress.js => progress/index.js} | 12 +- app/templates/progress/progress.css | 27 + .../{selectbox.js => selectbox/index.js} | 12 +- app/templates/selectbox/selectbox.css | 36 + app/templates/setPasswordSection/index.js | 49 + .../setPasswordSection/setPasswordSection.css | 56 + app/templates/uploadPasswordSet.js | 81 - app/templates/uploadPasswordUnset.js | 70 - app/utils.js | 8 +- assets/copy-16.svg | 2 +- assets/main.css | 1236 ---- browserslist | 3 +- package-lock.json | 5575 +++++++++++------ package.json | 12 +- postcss.config.js | 14 +- server/layout.js | 2 +- webpack.config.js | 25 +- 62 files changed, 5465 insertions(+), 3812 deletions(-) create mode 100644 app/base.css create mode 100644 app/main.css delete mode 100644 app/pages/completed.js create mode 100644 app/pages/completed/index.js delete mode 100644 app/pages/download.js create mode 100644 app/pages/download/index.js create mode 100644 app/pages/error/error.css rename app/pages/{error.js => error/index.js} (51%) rename app/pages/{notFound.js => notFound/index.js} (61%) create mode 100644 app/pages/notFound/notFound.css delete mode 100644 app/pages/preview.js create mode 100644 app/pages/preview/index.js create mode 100644 app/pages/preview/preview.css rename app/pages/{share.js => share/index.js} (52%) create mode 100644 app/pages/share/share.css delete mode 100644 app/pages/unsupported.js create mode 100644 app/pages/unsupported/index.js create mode 100644 app/pages/unsupported/unsupported.css delete mode 100644 app/pages/upload.js create mode 100644 app/pages/upload/index.js rename app/pages/{welcome.js => welcome/index.js} (63%) create mode 100644 app/pages/welcome/welcome.css create mode 100644 app/templates/changePasswordSection/changePasswordSection.css create mode 100644 app/templates/changePasswordSection/index.js create mode 100644 app/templates/downloadButton/downloadButton.css rename app/templates/{downloadButton.js => downloadButton/index.js} (86%) create mode 100644 app/templates/downloadPassword/downloadPassword.css rename app/templates/{downloadPassword.js => downloadPassword/index.js} (54%) create mode 100644 app/templates/file/file.css rename app/templates/{file.js => file/index.js} (60%) create mode 100644 app/templates/fileList/fileList.css rename app/templates/{fileList.js => fileList/index.js} (54%) delete mode 100644 app/templates/footer.js create mode 100644 app/templates/footer/footer.css create mode 100644 app/templates/footer/index.js create mode 100644 app/templates/fxPromo/fxPromo.css rename app/templates/{fxPromo.js => fxPromo/index.js} (79%) create mode 100644 app/templates/header/header.css rename app/templates/{header.js => header/index.js} (79%) create mode 100644 app/templates/passwordInput/index.js create mode 100644 app/templates/passwordInput/passwordInput.css create mode 100644 app/templates/popup/index.js create mode 100644 app/templates/popup/popup.css rename app/templates/{progress.js => progress/index.js} (78%) create mode 100644 app/templates/progress/progress.css rename app/templates/{selectbox.js => selectbox/index.js} (77%) create mode 100644 app/templates/selectbox/selectbox.css create mode 100644 app/templates/setPasswordSection/index.js create mode 100644 app/templates/setPasswordSection/setPasswordSection.css delete mode 100644 app/templates/uploadPasswordSet.js delete mode 100644 app/templates/uploadPasswordUnset.js delete mode 100644 assets/main.css diff --git a/app/base.css b/app/base.css new file mode 100644 index 000000000..cf0f0bf07 --- /dev/null +++ b/app/base.css @@ -0,0 +1,257 @@ +:root { + --pageBGColor: #fff; + --primaryControlBGColor: #0297f8; + --primaryControlFGColor: #fff; + --primaryControlHoverColor: #0287e8; + --inputTextColor: #737373; + --errorColor: #d70022; + --linkColor: #0094fb; + --textColor: #0c0c0d; + --lightTextColor: #737373; + --successControlBGColor: #05a700; + --successControlFGColor: #fff; +} + +html { + background: url('../assets/send_bg.svg'); + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui', + 'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif; + font-weight: 200; + background-size: 110%; + background-repeat: no-repeat; + background-position: center top; + height: 100%; + margin: auto; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'segoe ui', + 'helvetica neue', helvetica, ubuntu, roboto, noto, arial, sans-serif; + display: flex; + flex-direction: column; + margin: 0; + min-height: 100vh; + position: relative; +} + +pre, +input, +select, +textarea, +button { + font-family: inherit; + margin: 0; +} + +pre { + font-family: monospace; + font-size: 18px; + font-weight: 600; + display: inline-block; +} + +a { + text-decoration: none; +} + +.all { + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-start; + max-width: 650px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; + width: 96%; +} + +.btn { + font-size: 15px; + font-weight: 500; + color: white; + cursor: pointer; + text-align: center; + background: var(--primaryControlBGColor); + border: 1px solid var(--primaryControlBGColor); + border-radius: 5px; +} + +.btn--cancel { + color: var(--errorColor); + background: var(--pageBGColor); + font-size: 15px; + border: 0; + cursor: pointer; + text-decoration: underline; +} + +.btn--cancel:disabled { + text-decoration: none; + cursor: auto; +} + +.input { + border: 1px solid var(--primaryControlBGColor); + border-radius: 6px 0 0 6px; + font-size: 20px; + color: var(--inputTextColor); + font-family: 'SF Pro Text', sans-serif; + letter-spacing: 0; + line-height: 23px; + font-weight: 300; + height: 46px; + padding-left: 10px; + padding-right: 10px; +} + +.input--noBtn { + border-radius: 6px; +} + +.inputBtn { + background: var(--primaryControlBGColor); + border-radius: 0 6px 6px 0; + border: 1px solid var(--primaryControlBGColor); + color: white; + cursor: pointer; + + /* Force flat button look */ + appearance: none; + font-size: 15px; + padding-bottom: 3px; + padding-left: 10px; + padding-right: 10px; + white-space: nowrap; +} + +.inputBtn:disabled { + cursor: auto; +} + +.inputBtn:hover { + background-color: var(--primaryControlHoverColor); +} + +.inputBtn--hidden { + display: none; +} + +.cursor--pointer { + cursor: pointer; +} + +.link { + color: var(--linkColor); + text-decoration: none; +} + +.link:focus, +.link:active, +.link:hover { + color: var(--primaryControlHoverColor); +} + +.link--action { + text-decoration: underline; + text-align: center; +} + +.page { + margin: 0 auto 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; +} + +.progressSection { + margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; + font-size: 15px; +} + +.progressSection__text { + color: rgba(0, 0, 0, 0.5); + letter-spacing: -0.4px; + margin-top: 24px; + margin-bottom: 74px; +} + +.effect--fadeOut { + opacity: 0; + animation: fadeout 200ms linear; +} + +@keyframes fadeout { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.effect--fadeIn { + opacity: 1; + animation: fadein 200ms linear; +} + +@keyframes fadein { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.title { + font-size: 33px; + line-height: 40px; + margin: 20px auto; + text-align: center; + max-width: 520px; + font-family: 'SF Pro Text', sans-serif; + word-wrap: break-word; +} + +.description { + font-size: 15px; + line-height: 23px; + max-width: 630px; + text-align: center; + margin: 0 auto 60px; + color: var(--textColor); + width: 92%; +} + +@media (max-device-width: 768px), (max-width: 768px) { + .description { + margin: 0 auto 25px; + } +} + +@media (max-device-width: 520px), (max-width: 520px) { + .input { + font-size: 22px; + padding: 10px 10px; + border-radius: 6px 6px 0 0; + } + + .inputBtn { + border-radius: 0 0 6px 6px; + flex: 0 1 65px; + } + + .input--noBtn { + border-radius: 6px; + } +} diff --git a/app/fileManager.js b/app/fileManager.js index 26da0fc6b..7513d42d3 100644 --- a/app/fileManager.js +++ b/app/fileManager.js @@ -108,7 +108,7 @@ export default function(state, emitter) { document.getElementById('cancel-upload').hidden = 'hidden'; await delay(1000); - await fadeOut('upload-progress'); + await fadeOut('.page'); openLinksInNewTab(links, false); emitter.emit('pushState', `/share/${ownedFile.id}`); } catch (err) { @@ -170,7 +170,7 @@ export default function(state, emitter) { const time = Date.now() - start; const speed = size / (time / 1000); await delay(1000); - await fadeOut('download-progress'); + await fadeOut('.page'); saveFile(f); state.storage.totalDownloads += 1; state.transfer.reset(); diff --git a/app/main.css b/app/main.css new file mode 100644 index 000000000..9c7c5dbae --- /dev/null +++ b/app/main.css @@ -0,0 +1,19 @@ +@import './base.css'; +@import './templates/header/header.css'; +@import './templates/downloadButton/downloadButton.css'; +@import './templates/progress/progress.css'; +@import './templates/passwordInput/passwordInput.css'; +@import './templates/downloadPassword/downloadPassword.css'; +@import './templates/setPasswordSection/setPasswordSection.css'; +@import './templates/changePasswordSection/changePasswordSection.css'; +@import './templates/footer/footer.css'; +@import './templates/fxPromo/fxPromo.css'; +@import './templates/selectbox/selectbox.css'; +@import './templates/fileList/fileList.css'; +@import './templates/file/file.css'; +@import './templates/popup/popup.css'; +@import './pages/welcome/welcome.css'; +@import './pages/preview/preview.css'; +@import './pages/share/share.css'; +@import './pages/notFound/notFound.css'; +@import './pages/unsupported/unsupported.css'; diff --git a/app/pages/completed.js b/app/pages/completed.js deleted file mode 100644 index fa1fb896d..000000000 --- a/app/pages/completed.js +++ /dev/null @@ -1,34 +0,0 @@ -const html = require('choo/html'); -const progress = require('../templates/progress'); -const { fadeOut } = require('../utils'); - -module.exports = function(state, emit) { - const div = html` -
    -
    -
    -
    - ${state.translate('downloadFinish')} -
    -
    - ${progress(1)} -
    -
    -
    -
    - ${state.translate('sendYourFilesLink')} -
    -
    - `; - - async function sendNew(e) { - e.preventDefault(); - await fadeOut('download'); - emit('pushState', '/'); - } - - return div; -}; diff --git a/app/pages/completed/index.js b/app/pages/completed/index.js new file mode 100644 index 000000000..88ba37e34 --- /dev/null +++ b/app/pages/completed/index.js @@ -0,0 +1,28 @@ +const html = require('choo/html'); +const progress = require('../../templates/progress'); +const { fadeOut } = require('../../utils'); + +module.exports = function(state, emit) { + const div = html` +
    +
    + ${state.translate('downloadFinish')} +
    +
    + ${progress(1)} +
    +
    +
    + ${state.translate('sendYourFilesLink')} +
    `; + + async function sendNew(e) { + e.preventDefault(); + await fadeOut('.page'); + emit('pushState', '/'); + } + + return div; +}; diff --git a/app/pages/download.js b/app/pages/download.js deleted file mode 100644 index 4a415f17b..000000000 --- a/app/pages/download.js +++ /dev/null @@ -1,46 +0,0 @@ -const html = require('choo/html'); -const progress = require('../templates/progress'); -const { bytes } = require('../utils'); - -module.exports = function(state, emit) { - const transfer = state.transfer; - const cancelBtn = html` - `; - - const div = html` -
    -
    -
    -
    - ${state.translate('downloadingPageProgress', { - filename: state.fileInfo.name, - size: bytes(state.fileInfo.size) - })} -
    -
    - ${state.translate('downloadingPageMessage')} -
    - ${progress(transfer.progressRatio)} -
    -
    - ${state.translate(transfer.msg, transfer.sizes)} -
    - ${transfer.state === 'downloading' ? cancelBtn : null} -
    -
    -
    -
    - `; - - function cancel() { - const btn = document.getElementById('cancel-upload'); - btn.remove(); - emit('cancel'); - } - return div; -}; diff --git a/app/pages/download/index.js b/app/pages/download/index.js new file mode 100644 index 000000000..f17cf5c7f --- /dev/null +++ b/app/pages/download/index.js @@ -0,0 +1,43 @@ +const html = require('choo/html'); +const progress = require('../../templates/progress'); +const { bytes } = require('../../utils'); + +module.exports = function(state, emit) { + const transfer = state.transfer; + const cancelBtn = html` + `; + + const div = html` +
    +
    + ${state.translate('downloadingPageProgress', { + filename: state.fileInfo.name, + size: bytes(state.fileInfo.size) + })} +
    +
    + ${state.translate('downloadingPageMessage')} +
    + ${progress(transfer.progressRatio)} +
    +
    + ${state.translate(transfer.msg, transfer.sizes)} +
    + ${transfer.state === 'downloading' ? cancelBtn : null} +
    +
    + `; + + function cancel() { + const btn = document.getElementById('cancel'); + btn.remove(); + emit('cancel'); + } + return div; +}; diff --git a/app/pages/error/error.css b/app/pages/error/error.css new file mode 100644 index 000000000..2bf8739cd --- /dev/null +++ b/app/pages/error/error.css @@ -0,0 +1,11 @@ +.errorPage { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; +} + +.errorPage__img { + margin: 51px 0 71px; +} diff --git a/app/pages/error.js b/app/pages/error/index.js similarity index 51% rename from app/pages/error.js rename to app/pages/error/index.js index f7751faa7..214467894 100644 --- a/app/pages/error.js +++ b/app/pages/error/index.js @@ -1,10 +1,10 @@ const html = require('choo/html'); -const assets = require('../../common/assets'); +const assets = require('../../../common/assets'); module.exports = function(state) { return html` -
    +
    ${state.translate('errorPageHeader')}
    - +
    `; }; diff --git a/app/pages/legal.js b/app/pages/legal.js index 3b92b4d1c..5b086ce7b 100644 --- a/app/pages/legal.js +++ b/app/pages/legal.js @@ -12,23 +12,21 @@ function replaceLinks(str, urls) { module.exports = function(state) { const div = html` -
    - + `; return div; diff --git a/app/pages/notFound.js b/app/pages/notFound/index.js similarity index 61% rename from app/pages/notFound.js rename to app/pages/notFound/index.js index 495b8b082..f8b322913 100644 --- a/app/pages/notFound.js +++ b/app/pages/notFound/index.js @@ -1,21 +1,19 @@ const html = require('choo/html'); -const assets = require('../../common/assets'); +const assets = require('../../../common/assets'); module.exports = function(state) { const div = html` -
    -
    +
    ${state.translate('expiredPageHeader')}
    - `; +
    `; return div; }; diff --git a/app/pages/notFound/notFound.css b/app/pages/notFound/notFound.css new file mode 100644 index 000000000..07bf0e151 --- /dev/null +++ b/app/pages/notFound/notFound.css @@ -0,0 +1,17 @@ +.notFoundPage { + margin: 0 auto 30px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; +} + +.notFoundPage__img { + margin: 0 auto; + display: flex; + justify-content: center; + flex-direction: column; + width: 100%; + max-width: 640px; +} diff --git a/app/pages/preview.js b/app/pages/preview.js deleted file mode 100644 index 84e54f250..000000000 --- a/app/pages/preview.js +++ /dev/null @@ -1,44 +0,0 @@ -const html = require('choo/html'); -const assets = require('../../common/assets'); -const { bytes } = require('../utils'); - -module.exports = function(state, pageAction) { - const fileInfo = state.fileInfo; - - const size = fileInfo.size - ? state.translate('downloadFileSize', { size: bytes(fileInfo.size) }) - : ''; - - const title = fileInfo.name - ? state.translate('downloadFileName', { filename: fileInfo.name }) - : state.translate('downloadFileTitle'); - - const info = html` -
    `; - if (!pageAction) { - return info; - } - const div = html` -
    -
    -
    -
    - ${title} - ${' ' + size} -
    -
    ${state.translate('downloadMessage')}
    - - ${pageAction} -
    - ${state.translate('sendYourFilesLink')} -
    - ${info} -
    - `; - return div; -}; diff --git a/app/pages/preview/index.js b/app/pages/preview/index.js new file mode 100644 index 000000000..8c9f9d1d8 --- /dev/null +++ b/app/pages/preview/index.js @@ -0,0 +1,42 @@ +const html = require('choo/html'); +const assets = require('../../../common/assets'); +const { bytes } = require('../../utils'); + +module.exports = function(state, pageAction) { + const fileInfo = state.fileInfo; + + const size = fileInfo.size + ? state.translate('downloadFileSize', { size: bytes(fileInfo.size) }) + : ''; + + const title = fileInfo.name + ? state.translate('downloadFileName', { filename: fileInfo.name }) + : state.translate('downloadFileTitle'); + + const info = html` +
    `; + if (!pageAction) { + return info; + } + const div = html` +
    +
    + ${title} + ${' ' + size} +
    +
    ${state.translate('downloadMessage')}
    + + ${pageAction} + + ${state.translate('sendYourFilesLink')} + + ${info} +
    + `; + return div; +}; diff --git a/app/pages/preview/preview.css b/app/pages/preview/preview.css new file mode 100644 index 000000000..83b1bc987 --- /dev/null +++ b/app/pages/preview/preview.css @@ -0,0 +1,4 @@ +.preview__img { + width: 283px; + height: 196px; +} diff --git a/app/pages/share.js b/app/pages/share/index.js similarity index 52% rename from app/pages/share.js rename to app/pages/share/index.js index aa16a8e5f..1180b3cb2 100644 --- a/app/pages/share.js +++ b/app/pages/share/index.js @@ -1,12 +1,13 @@ /* global EXPIRE_SECONDS */ const html = require('choo/html'); const raw = require('choo/html/raw'); -const assets = require('../../common/assets'); -const notFound = require('./notFound'); -const uploadPasswordSet = require('../templates/uploadPasswordSet'); -const uploadPasswordUnset = require('../templates/uploadPasswordUnset'); -const selectbox = require('../templates/selectbox'); -const { allowedCopy, delay, fadeOut } = require('../utils'); +const assets = require('../../../common/assets'); +const notFound = require('../notFound'); +const changePasswordSection = require('../../templates/changePasswordSection'); +const setPasswordSection = require('../../templates/setPasswordSection'); +const selectbox = require('../../templates/selectbox'); +const deletePopup = require('../../templates/popup'); +const { allowedCopy, delay, fadeOut } = require('../../utils'); function expireInfo(file, translate, emit) { const hours = Math.floor(EXPIRE_SECONDS / 60 / 60); @@ -34,44 +35,42 @@ module.exports = function(state, emit) { } const passwordSection = file.hasPassword - ? uploadPasswordSet(state, emit) - : uploadPasswordUnset(state, emit); + ? changePasswordSection(state, emit) + : setPasswordSection(state, emit); const div = html` -