diff --git a/.gitignore b/.gitignore index a7cd074..660849d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -.idea/ -node_modules/ -bower_components/ -test/app/public/ -.eslintrc.js .editorconfig +.eslintrc.js + +/.idea/ +/bower_components/ +/node_modules/ +/test/app/public/ diff --git a/Makefile b/Makefile index 32a8b15..8b2520c 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,6 @@ include n.Makefile -test: verify - karma start +unit-test: + karma start test/karma.conf.js -test-app: - rm -rf test/app/public/ - mkdir test/app/public/ - browserify test/app/main.js --debug --transform debowerify > test/app/public/bundle.js - node test/app/server.js +test: verify unit-test diff --git a/bower.json b/bower.json index 873675b..3ae7e48 100644 --- a/bower.json +++ b/bower.json @@ -12,9 +12,5 @@ "bower_components", "test", "tests" - ], - "devDpendencies": { - "fetch": "github/fetch#^0.9.0", - "es6-promise": "^2.1.0" - } + ] } diff --git a/circle.yml b/circle.yml index d6eaee7..8e7b29b 100644 --- a/circle.yml +++ b/circle.yml @@ -1,9 +1,3 @@ machine: node: version: 4.4.7 -deployment: - release: - tag: /^v[0-9]+(.[0-9]+)*/ - owner: Financial-Times - commands: - - make npm-publish diff --git a/main.js b/main.js index ecb761b..644e794 100644 --- a/main.js +++ b/main.js @@ -1,55 +1,78 @@ -'use strict'; -const request = require('./src/request'); -const cache = require('./src/cache'); -const promises = {}; - -function uuid (){ - const uuid = cache('uuid') - - if (uuid){ - return Promise.resolve({ - uuid:uuid - }); +import request from './src/request'; +import cache from './src/cache'; + +const requests = {}; + +// DEPRECATED: use the secure session ID, via getSessionId +const getCookie = () => { + return (/FTSession=([^;]+)/.exec(document.cookie) || [null, ''])[1]; +}; + +const getSessionId = () => { + const [, sessionId] = /FTSession_s=([^;]+)/.exec(document.cookie) || []; + return sessionId; +}; + +const getUuid = () => { + const cachedUUID = cache('uuid'); + if (cachedUUID) { + return Promise.resolve({ uuid: cachedUUID }); } - if (!promises.uuid) { - promises.uuid = request('/uuid').then(function (response){ - cache('uuid', response.uuid); - return response; - }); + + const sessionId = getSessionId(); + if (!sessionId) { + return Promise.resolve({ uuid: undefined }); } - return promises.uuid; -} + if (!requests.uuid) { + requests.uuid = request(`/sessions/s/${sessionId}`) + .then(({ uuid } = {}) => { + delete requests.uuid; + if (uuid) { + cache('uuid', uuid); + } + return { uuid }; + }); + } + + return requests.uuid; +}; -function products () { +const getProducts = () => { const cachedProducts = cache('products'); const cachedUUID = cache('uuid'); - - if(cachedProducts && cachedUUID){ - return Promise.resolve({products:cachedProducts, uuid:cachedUUID}); + if (cachedProducts && cachedUUID){ + return Promise.resolve({ products: cachedProducts, uuid: cachedUUID }); } - if (!promises.products) { - promises.products = request('/products').then(function (response) { - cache('products', response.products); - cache('uuid', response.uuid); - return response; - }); + if (!requests.products) { + requests.products = request('/products', { credentials: 'include' }) + .then(({ products, uuid } = {}) => { + delete requests.products; + if (products) { + cache('uuid', uuid); + } + if (uuid) { + cache('uuid', uuid); + } + return { products, uuid }; + }); } - return promises.products; -} + return requests.products; +}; -function validate () { - return request('/validate'); -} +// DEPRECATED: use getUuid, will only return a uuid if session is valid +const validate = () => { + return getUuid() + .then(({ uuid }) => uuid ? true : false); +}; -module.exports = { - uuid : uuid, - validate : validate, - cache : cache, - products: products, - cookie : function () { - return (/FTSession=([^;]+)/.exec(document.cookie) || [null, ''])[1]; - } +export default { + uuid: getUuid, + products: getProducts, + validate, + cache, + cookie: getCookie, + sessionId: getSessionId }; diff --git a/n.Makefile b/n.Makefile index e23f86d..97d7e8b 100644 --- a/n.Makefile +++ b/n.Makefile @@ -14,6 +14,8 @@ endif # Enforce repo ownership ifeq ("$(wildcard ft.yml)","") $(error 'Projects making use of n-makefile *must* define an ft.yml file containing the repo owner's details (see any next- repo for required structure)') +$(error 'If you are creating a project not to be maintained by the next team please feel free to copy what you need from our build tools but don't add an ft.yml.') +$(error 'Integrating with our tooling may result in unwanted effects e.g. nightly builds, slack alerts, emails etc') endif # ./node_modules/.bin on the PATH @@ -28,7 +30,7 @@ NPM_INSTALL = npm prune --production=false && npm install BOWER_INSTALL = bower prune && bower install --config.registry.search=http://registry.origami.ft.com --config.registry.search=https://bower.herokuapp.com JSON_GET_VALUE = grep $1 | head -n 1 | sed 's/[," ]//g' | cut -d : -f 2 IS_GIT_IGNORED = grep -q $(if $1, $1, $@) .gitignore -VERSION = v14 +VERSION = v15 APP_NAME = $(shell cat package.json 2>/dev/null | $(call JSON_GET_VALUE,name)) DONE = echo ✓ $@ done CONFIG_VARS = curl -fsL https://ft-next-config-vars.herokuapp.com/$1/$(call APP_NAME)$(if $2,.$2,) -H "Authorization: `heroku config:get APIKEY --app ft-next-config-vars`" diff --git a/package.json b/package.json index 3a24a2f..6c074ea 100644 --- a/package.json +++ b/package.json @@ -20,23 +20,25 @@ }, "homepage": "https://github.com/Financial-Times/next-session-component", "devDependencies": { + "babel-loader": "^6.4.0", + "babel-preset-es2015": "^6.24.0", "bower": "^1.7.9", - "browserify": "^10.1.3", - "chai": "^2.3.0", - "debowerify": "^1.2.1", - "eslint": "^3.0.1", + "chai": "^3.0.0", + "eslint": "^3.16.1", "express": "^4.12.3", - "fetch-mock": "^1.4.1", - "isomorphic-fetch": "^2.0.2", - "karma": "^0.12.31", - "karma-browserify": "^4.1.2", - "karma-chrome-launcher": "^0.1.10", - "karma-mocha": "^0.1.10", - "karma-phantomjs-launcher": "^0.1.4", - "lintspaces-cli": "^0.4.0", - "mocha": "^2.2.4", - "npm-prepublish": "^1.2.1", - "origami-build-tools": "^2.13.0", - "sinon": "^1.14.1" + "karma": "^1.5.0", + "karma-chai": "^0.1.0", + "karma-cli": "^1.0.1", + "karma-firefox-launcher": "^1.0.0", + "karma-mocha": "^1.3.0", + "karma-sinon": "^1.0.5", + "karma-webpack": "^2.0.3", + "lintspaces-cli": "^0.6.0", + "mocha": "^3.2.0", + "sinon": "^1.14.1", + "webpack": "^2.2.1" + }, + "engines": { + "node": "4.4.7" } } diff --git a/src/cache.js b/src/cache.js index 86bb274..f24e20a 100644 --- a/src/cache.js +++ b/src/cache.js @@ -1,32 +1,29 @@ -'use strict'; - let detailsCache = {}; - -function cache (name, value) { - if(typeof name === 'object'){ +const cache = (name, value) => { + if (typeof name === 'object'){ detailsCache = name; return; } - if(typeof name === 'string' && typeof value === 'string') { + if (typeof name === 'string' && typeof value === 'string') { detailsCache[name] = value; return; } - if(typeof name === 'string' && typeof value === 'undefined') { + if (typeof name === 'string' && typeof value === 'undefined') { return detailsCache[name] || null; } - if(typeof name === 'undefined' && typeof value === 'undefined') { + if (typeof name === 'undefined' && typeof value === 'undefined') { return detailsCache; } throw new Error('Invalid arguments'); } -cache.clear = function () { +cache.clear = () => { detailsCache = {}; }; -module.exports = cache; +export default cache; diff --git a/src/request.js b/src/request.js index be6b52b..45a3a95 100644 --- a/src/request.js +++ b/src/request.js @@ -1,38 +1,27 @@ -'use strict'; - -function request (url) { - // if we don't have a session token cookie, don't bother.. - if (document.cookie.indexOf('FTSession=') === -1) { - return Promise.reject(new Error('No session cookie found')); - } - +export default (url, { credentials = 'omit' } = {}) => { return fetch(`https://session-next.ft.com${url}`, { - credentials:'include', + credentials, useCorsProxy: true - }).then(function (response) { - if (response.ok){ - return (url === '/validate') ? Promise.resolve(true) : response.json(); - } else { - return (url === '/validate') ? Promise.resolve(false) : Promise.reject(response.status); - } - - }).catch(function (e) { - const message = e.message || ''; - if (message.indexOf('timed out') > -1 || message.indexOf('Network request failed') > -1 || message.indexOf('Not Found') > -1) { - // HTTP timeouts and invalid sessions are a fact of life on the internet. - // We don't want to report this to Sentry. - } else { + }) + .then(response => { + if (response.ok){ + return response.json(); + } else { + return response.text() + .then(text => { + throw new Error(`Next session responded with "${text}" (${response.status})`); + }); + } + }) + .catch(err => { document.body.dispatchEvent(new CustomEvent('oErrors.log', { bubbles: true, detail: { - error: e, + error: err, info: { component: 'next-session-client' } } - })) - } - }); + })); + }); } - -module.exports = request; diff --git a/test/app/index.html b/test/app/index.html deleted file mode 100644 index b2e6c03..0000000 --- a/test/app/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - -
- -