Client: i18n (#8972)

* wip: client: i18n

* remove maxAge from cookies to get same expiration ad localStorage

* set cookies expiration to 10 years

* moment: load translations in browser, moment: only load necessary data, remove jquery, remove bluebird

* ability to change language

* fix logout

* add some requiresLogin: false to static pages

* fix tests
This commit is contained in:
Matteo Pagliazzi
2017-08-22 18:26:53 +02:00
committed by GitHub
parent e5a92f64c0
commit bd46e3e195
21 changed files with 163 additions and 92 deletions

24
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "habitica", "name": "habitica",
"version": "3.110.0", "version": "3.111.0",
"dependencies": { "dependencies": {
"@gulp-sourcemaps/map-sources": { "@gulp-sourcemaps/map-sources": {
"version": "1.0.0", "version": "1.0.0",
@@ -2433,13 +2433,11 @@
"version": "4.0.0", "version": "4.0.0",
"from": "cross-env@>=4.0.0 <5.0.0", "from": "cross-env@>=4.0.0 <5.0.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-4.0.0.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-4.0.0.tgz",
"dev": true,
"dependencies": { "dependencies": {
"is-windows": { "is-windows": {
"version": "1.0.1", "version": "1.0.1",
"from": "is-windows@>=1.0.0 <2.0.0", "from": "is-windows@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz"
"dev": true
} }
} }
}, },
@@ -2447,19 +2445,16 @@
"version": "5.1.0", "version": "5.1.0",
"from": "cross-spawn@>=5.0.1 <6.0.0", "from": "cross-spawn@>=5.0.1 <6.0.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": { "lru-cache": {
"version": "4.1.1", "version": "4.1.1",
"from": "lru-cache@>=4.0.1 <5.0.0", "from": "lru-cache@>=4.0.1 <5.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz"
"dev": true
}, },
"which": { "which": {
"version": "1.3.0", "version": "1.3.0",
"from": "which@>=1.2.9 <2.0.0", "from": "which@>=1.2.9 <2.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
"dev": true
} }
} }
}, },
@@ -7385,11 +7380,6 @@
"resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz", "resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz",
"optional": true "optional": true
}, },
"jquery": {
"version": "3.2.1",
"from": "jquery@>=3.1.1 <4.0.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz"
},
"js-base64": { "js-base64": {
"version": "2.1.9", "version": "2.1.9",
"from": "js-base64@>=2.1.9 <3.0.0", "from": "js-base64@>=2.1.9 <3.0.0",
@@ -11687,14 +11677,12 @@
"shebang-command": { "shebang-command": {
"version": "1.2.0", "version": "1.2.0",
"from": "shebang-command@>=1.2.0 <2.0.0", "from": "shebang-command@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz"
"dev": true
}, },
"shebang-regex": { "shebang-regex": {
"version": "1.0.0", "version": "1.0.0",
"from": "shebang-regex@>=1.0.0 <2.0.0", "from": "shebang-regex@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
"dev": true
}, },
"shell-quote": { "shell-quote": {
"version": "1.4.3", "version": "1.4.3",

View File

@@ -75,7 +75,6 @@
"in-app-purchase": "^1.1.6", "in-app-purchase": "^1.1.6",
"intro.js": "^2.6.0", "intro.js": "^2.6.0",
"jade": "~1.11.0", "jade": "~1.11.0",
"jquery": "^3.1.1",
"js2xmlparser": "~1.0.0", "js2xmlparser": "~1.0.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0",

View File

@@ -4,7 +4,13 @@ import Vue from 'vue';
describe('i18n plugin', () => { describe('i18n plugin', () => {
before(() => { before(() => {
Vue.use(i18n); Vue.use(i18n, {
i18nData: {
strings: {
reportBug: 'Report a Bug',
},
},
});
}); });
it('adds $t to Vue.prototype', () => { it('adds $t to Vue.prototype', () => {

View File

@@ -21,7 +21,7 @@ module.exports = {
productionGzipExtensions: ['js', 'css'], productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to // Run the build command with an extra argument to
// View the bundle analyzer report after build finishes: // View the bundle analyzer report after build finishes:
// `npm run build --report` // `npm run client:build --report`
// Set to `true` or `false` to always turn it on or off // Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report, // eslint-disable-line no-process-env bundleAnalyzerReport: process.env.npm_config_report, // eslint-disable-line no-process-env
}, },
@@ -50,6 +50,10 @@ module.exports = {
target: 'http://localhost:3000', target: 'http://localhost:3000',
changeOrigin: true, changeOrigin: true,
}, },
'/logout': {
target: 'http://localhost:3000',
changeOrigin: true,
},
}, },
// CSS Sourcemaps off by default because relative paths are "buggy" // CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README // with this option, according to the CSS-Loader README

View File

@@ -3,8 +3,8 @@
const path = require('path'); const path = require('path');
const config = require('./config'); const config = require('./config');
const utils = require('./utils'); const utils = require('./utils');
const projectRoot = path.resolve(__dirname, '../');
const webpack = require('webpack'); const webpack = require('webpack');
const projectRoot = path.resolve(__dirname, '../');
const autoprefixer = require('autoprefixer'); const autoprefixer = require('autoprefixer');
const postcssEasyImport = require('postcss-easy-import'); const postcssEasyImport = require('postcss-easy-import');
const IS_PROD = process.env.NODE_ENV === 'production'; const IS_PROD = process.env.NODE_ENV === 'production';
@@ -36,7 +36,6 @@ const baseConfig = {
path.join(projectRoot, 'node_modules'), path.join(projectRoot, 'node_modules'),
], ],
alias: { alias: {
jquery: 'jquery/src/jquery',
website: path.resolve(projectRoot, 'website'), website: path.resolve(projectRoot, 'website'),
common: path.resolve(projectRoot, 'website/common'), common: path.resolve(projectRoot, 'website/common'),
client: path.resolve(projectRoot, 'website/client'), client: path.resolve(projectRoot, 'website/client'),
@@ -45,10 +44,7 @@ const baseConfig = {
}, },
}, },
plugins: [ plugins: [
new webpack.ProvidePlugin({ new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(NOT_EXISTING)$/),
$: 'jquery',
jQuery: 'jquery',
}),
], ],
module: { module: {
rules: [ rules: [

View File

@@ -6,9 +6,6 @@
"plugins": [ "plugins": [
"html" "html"
], ],
"globals": {
"$": true,
},
"parser": "babel-eslint", "parser": "babel-eslint",
"rules": { "rules": {
"strict": 0 "strict": 0

View File

@@ -262,7 +262,7 @@ export default {
methods: { methods: {
logout () { logout () {
localStorage.removeItem('habit-mobile-settings'); localStorage.removeItem('habit-mobile-settings');
this.$router.go('/'); window.location.href = '/logout';
}, },
showInbox () { showInbox () {
this.$root.$emit('show::modal', 'inbox-modal'); this.$root.$emit('show::modal', 'inbox-modal');

View File

@@ -125,7 +125,6 @@
<script> <script>
import axios from 'axios'; import axios from 'axios';
import Bluebird from 'bluebird';
import moment from 'moment'; import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { mapState } from 'client/libs/store'; import { mapState } from 'client/libs/store';
@@ -203,7 +202,7 @@ export default {
} }
}); });
let results = await Bluebird.all(promises); let results = await Promise.all(promises);
results.forEach(result => { results.forEach(result => {
let userData = result.data.data; let userData = result.data.data;
this.$set(this.cachedProfileData, userData._id, userData); this.$set(this.cachedProfileData, userData._id, userData);

View File

@@ -7,8 +7,8 @@
.col-6 .col-6
.form-horizontal .form-horizontal
h5 {{ $t('language') }} h5 {{ $t('language') }}
select.form-control(v-model='selectedLanguage', select.form-control(:value='user.preferences.language',
@change='changeLanguage()') @change='changeLanguage($event)')
option(v-for='lang in availableLanguages', :value='lang.code') {{lang.name}} option(v-for='lang in availableLanguages', :value='lang.code') {{lang.name}}
small small
@@ -218,13 +218,7 @@ export default {
return { return {
SOCIAL_AUTH_NETWORKS: [], SOCIAL_AUTH_NETWORKS: [],
party: {}, party: {},
// @TODO: import // Made available by the server as a script
availableLanguages: [
{
code: 'en',
name: 'English',
},
],
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
dayStartOptions, dayStartOptions,
newDayStart: 0, newDayStart: 0,
@@ -240,7 +234,10 @@ export default {
this.newDayStart = this.user.preferences.dayStart; this.newDayStart = this.user.preferences.dayStart;
}, },
computed: { computed: {
...mapState({user: 'user.data'}), ...mapState({
user: 'user.data',
availableLanguages: 'i18n.availableLanguages',
}),
timezoneOffsetToUtc () { timezoneOffsetToUtc () {
let offset = this.user.preferences.timezoneOffset; let offset = this.user.preferences.timezoneOffset;
let sign = offset > 0 ? '-' : '+'; let sign = offset > 0 ? '-' : '+';
@@ -254,9 +251,6 @@ export default {
return `UTC${sign}${hour}:${minutes}`; return `UTC${sign}${hour}:${minutes}`;
}, },
selectedLanguage () {
return this.user.preferences.language;
},
dayStart () { dayStart () {
return this.user.preferences.dayStart; return this.user.preferences.dayStart;
}, },
@@ -327,9 +321,11 @@ export default {
// @TODO // @TODO
// Notification.text(response.data.data.message); // Notification.text(response.data.data.message);
}, },
changeLanguage () { changeLanguage (e) {
this.user.preferences.language = this.selectedLanguage.code; const newLang = e.target.value;
this.user.preferences.language = newLang;
this.set('language'); this.set('language');
window.location.href = '/';
}, },
async changeUser (attribute, updates) { async changeUser (attribute, updates) {
await axios.put(`/api/v3/user/auth/update-${attribute}`, updates); await axios.put(`/api/v3/user/auth/update-${attribute}`, updates);

View File

@@ -112,12 +112,14 @@ import filter from 'lodash/filter';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import min from 'lodash/min'; import min from 'lodash/min';
import { mapState } from 'client/libs/store'; import { mapState } from 'client/libs/store';
import encodeParams from 'client/libs/encodeParams';
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks'; import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
import planGemLimits from '../../../common/script/libs/planGemLimits'; import planGemLimits from '../../../common/script/libs/planGemLimits';
import amazonPaymentsModal from '../payments/amazonModal'; import amazonPaymentsModal from '../payments/amazonModal';
import paymentsMixin from '../../mixins/payments'; import paymentsMixin from '../../mixins/payments';
// TODO
const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh'; const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh';
export default { export default {
@@ -314,7 +316,7 @@ export default {
queryParams.groupId = group._id; queryParams.groupId = group._id;
} }
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${$.param(queryParams)}`; let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
await axios.get(cancelUrl); await axios.get(cancelUrl);
// Success // Success
alert(this.$t('paypalCanceled')); alert(this.$t('paypalCanceled'));

View File

@@ -10,11 +10,11 @@
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<script async type='text/javascript' <!-- Translations -->
src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'> <script type='text/javascript' src='/api/v3/i18n/browser-script'></script>
</script>
<script src="https://checkout.stripe.com/v2/checkout.js"></script> <script async type='text/javascript' src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'></script>
</script> <script async type='text/javascript' src="https://checkout.stripe.com/v2/checkout.js"></script>
<script> <script>
// Amplitude // Amplitude
// var r = window.amplitude || {}; // var r = window.amplitude || {};

View File

@@ -0,0 +1,7 @@
// Equivalent of jQuery's param
export default function (params) {
Object.keys(params).map((k) => {
return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`;
}).join('&');
}

View File

@@ -2,20 +2,31 @@
// Can be anywhere inside vue as 'this.$t' or '$t' in templates. // Can be anywhere inside vue as 'this.$t' or '$t' in templates.
import i18n from 'common/script/i18n'; import i18n from 'common/script/i18n';
import moment from 'moment';
// Load all english translations
// TODO it's a workaround until proper translation loading works
const context = require.context('common/locales/en', true, /\.(json)$/);
const translations = {};
context.keys().forEach(filename => {
Object.assign(translations, context(filename));
});
i18n.strings = translations;
export default { export default {
install (Vue) { install (Vue, {i18nData}) {
if (i18nData) {
// Load i18n strings
i18n.strings = i18nData.strings;
// Load Moment.js locale
const language = i18nData.language;
if (language && i18nData.momentLang && language.momentLangCode) {
// Make moment available under `window` so that the locale can be set
window.moment = moment;
// Execute the script and set the locale
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.type = 'text/javascript';
script.text = i18nData.momentLang;
head.appendChild(script);
moment.locale(language.momentLangCode);
}
}
Vue.prototype.$t = function translateString () { Vue.prototype.$t = function translateString () {
return i18n.t.apply(null, arguments); return i18n.t.apply(null, arguments);
}; };

View File

@@ -24,7 +24,9 @@ Vue.config.performance = !IS_PRODUCTION;
Vue.config.productionTip = IS_PRODUCTION; Vue.config.productionTip = IS_PRODUCTION;
Vue.use(Notifications); Vue.use(Notifications);
Vue.use(i18n);
// window['habitica-i18n] is injected by the server
Vue.use(i18n, {i18nData: window && window['habitica-i18n']});
Vue.use(StoreModule); Vue.use(StoreModule);
export default new Vue({ export default new Vue({

View File

@@ -243,24 +243,24 @@ const router = new VueRouter({
path: '/static', path: '/static',
component: ParentPage, component: ParentPage,
children: [ children: [
{ name: 'app', path: 'app', component: AppPage }, { name: 'app', path: 'app', component: AppPage, meta: {requiresLogin: false}},
{ name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage }, { name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: {requiresLogin: false}},
{ name: 'communitGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage }, { name: 'communitGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: {requiresLogin: false}},
{ name: 'contact', path: 'contact', component: ContactPage }, { name: 'contact', path: 'contact', component: ContactPage, meta: {requiresLogin: false}},
{ name: 'faq', path: 'faq', component: FAQPage }, { name: 'faq', path: 'faq', component: FAQPage, meta: {requiresLogin: false}},
{ name: 'features', path: 'features', component: FeaturesPage }, { name: 'features', path: 'features', component: FeaturesPage, meta: {requiresLogin: false}},
{ name: 'front', path: 'front', component: FrontPage }, { name: 'front', path: 'front', component: FrontPage, meta: {requiresLogin: false}},
{ name: 'groupPlans', path: 'group-plans', component: GroupPlansPage }, { name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: {requiresLogin: false}},
{ name: 'maintenance', path: 'maintenance', component: MaintenancePage }, { name: 'maintenance', path: 'maintenance', component: MaintenancePage, meta: {requiresLogin: false}},
{ name: 'maintenance-info', path: 'maintenance-info', component: MaintenanceInfoPage }, { name: 'maintenance-info', path: 'maintenance-info', component: MaintenanceInfoPage, meta: {requiresLogin: false}},
{ name: 'merch', path: 'merch', component: MerchPage }, { name: 'merch', path: 'merch', component: MerchPage, meta: {requiresLogin: false}},
// { name: 'newStuff', path: 'newStuff', component: NewStuffPage }, // { name: 'newStuff', path: 'newStuff', component: NewStuffPage, meta: {requiresLogin: false}},
{ name: 'overview', path: 'overview', component: OverviewPage }, { name: 'overview', path: 'overview', component: OverviewPage, meta: {requiresLogin: false}},
{ name: 'plans', path: 'plans', component: GroupPlansPage }, { name: 'plans', path: 'plans', component: GroupPlansPage, meta: {requiresLogin: false}},
{ name: 'pressKit', path: 'press-kit', component: PressKitPage }, { name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: {requiresLogin: false}},
{ name: 'privacy', path: 'privacy', component: PrivacyPage, meta: {requiresLogin: false}}, { name: 'privacy', path: 'privacy', component: PrivacyPage, meta: {requiresLogin: false}},
{ name: 'terms', path: 'terms', component: TermsPage, meta: {requiresLogin: false}}, { name: 'terms', path: 'terms', component: TermsPage, meta: {requiresLogin: false}},
{ name: 'videos', path: 'videos', component: VideosPage }, { name: 'videos', path: 'videos', component: VideosPage, meta: {requiresLogin: false}},
], ],
}, },
{ {

View File

@@ -25,6 +25,16 @@ if (AUTH_SETTINGS) {
isUserLoggedIn = true; isUserLoggedIn = true;
} }
const i18nData = window && window['habitica-i18n'];
let availableLanguages = [];
let selectedLanguage = {};
if (i18nData) {
availableLanguages = i18nData.availableLanguages;
selectedLanguage = i18nData.language;
}
// Export a function that generates the store and not the store directly // Export a function that generates the store and not the store directly
// so that we can regenerate it multiple times for testing, when not testing // so that we can regenerate it multiple times for testing, when not testing
// always export the same route // always export the same route
@@ -73,6 +83,10 @@ export default function () {
// NOTE this takes about 10-15ms on a fast computer // NOTE this takes about 10-15ms on a fast computer
content: deepFreeze(content), content: deepFreeze(content),
constants: deepFreeze({...commonConstants, DAY_MAPPING}), constants: deepFreeze({...commonConstants, DAY_MAPPING}),
i18n: deepFreeze({
availableLanguages,
selectedLanguage,
}),
hideHeader: false, hideHeader: false,
viewingMembers: [], viewingMembers: [],
openedItemRows: [], openedItemRows: [],

View File

@@ -101,6 +101,7 @@ async function saveContentToDisk (language, content) {
api.getContent = { api.getContent = {
method: 'GET', method: 'GET',
url: '/content', url: '/content',
noLanguage: true,
async handler (req, res) { async handler (req, res) {
let language = 'en'; let language = 'en';
let proposedLang = req.query.language && req.query.language.toString(); let proposedLang = req.query.language && req.query.language.toString();

View File

@@ -0,0 +1,46 @@
import {
translations,
momentLangs,
availableLanguages,
} from '../../libs/i18n';
import _ from 'lodash';
const api = {};
function geti18nBrowserScript (language) {
const langCode = language.code;
return `(function () {
if (!window) return;
window['habitica-i18n'] = ${JSON.stringify({
availableLanguages,
language,
strings: translations[langCode],
momentLang: momentLangs[language.momentLangCode],
})};
})()`;
}
/**
* @api {get} /api/v3/i18n/browser-script Returns a JS script to make all the i18n strings available in the browser
* under window.i18n.strings
* @apiDescription Does not require authentication.
* @apiName i18nBrowserScriptGet
* @apiGroup i18n
*/
api.geti18nBrowserScript = {
method: 'GET',
url: '/i18n/browser-script',
async handler (req, res) {
const language = _.find(availableLanguages, {code: req.language});
res.set({
'Content-Type': 'application/javascript',
});
const jsonResString = geti18nBrowserScript(language);
res.status(200).send(jsonResString);
},
};
module.exports = api;

View File

@@ -90,6 +90,7 @@ api.getFrontPage = {
api.getNewClient = { api.getNewClient = {
method: 'GET', method: 'GET',
url: '/', url: '/',
noLanguage: true,
async handler (req, res) { async handler (req, res) {
return res.sendFile('./dist-client/index.html', {root: `${__dirname}/../../../../`}); return res.sendFile('./dist-client/index.html', {root: `${__dirname}/../../../../`});
}, },

View File

@@ -25,14 +25,16 @@ module.exports.readController = function readController (router, controller) {
let middlewaresToAdd = [getUserLanguage]; let middlewaresToAdd = [getUserLanguage];
if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage and cron after authentication if (action.noLanguage !== true) {
if (authMiddlewareIndex === middlewares.length - 1) { if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage after authentication
middlewares.push(...middlewaresToAdd); if (authMiddlewareIndex === middlewares.length - 1) {
} else { middlewares.push(...middlewaresToAdd);
middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd); } else {
middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd);
}
} else { // no auth, getUserLanguage as the first middleware
middlewares.unshift(...middlewaresToAdd);
} }
} else { // no auth, getUserLanguage as the first middleware
middlewares.unshift(...middlewaresToAdd);
} }
method = method.toLowerCase(); method = method.toLowerCase();

View File

@@ -34,7 +34,7 @@ const ENABLE_HTTP_AUTH = nconf.get('SITE_HTTP_AUTH:ENABLED') === 'true';
// const PUBLIC_DIR = path.join(__dirname, '/../../client'); // const PUBLIC_DIR = path.join(__dirname, '/../../client');
const SESSION_SECRET = nconf.get('SESSION_SECRET'); const SESSION_SECRET = nconf.get('SESSION_SECRET');
const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; const TEN_YEARS = 1000 * 60 * 60 * 24 * 365 * 10;
module.exports = function attachMiddlewares (app, server) { module.exports = function attachMiddlewares (app, server) {
app.set('view engine', 'jade'); app.set('view engine', 'jade');
@@ -68,7 +68,7 @@ module.exports = function attachMiddlewares (app, server) {
secret: SESSION_SECRET, secret: SESSION_SECRET,
httpOnly: true, // so cookies are not accessible with browser JS httpOnly: true, // so cookies are not accessible with browser JS
// TODO what about https only (secure) ? // TODO what about https only (secure) ?
maxAge: TWO_WEEKS, maxAge: TEN_YEARS,
})); }));
// Initialize Passport! Also use passport.session() middleware, to support // Initialize Passport! Also use passport.session() middleware, to support