diff --git a/helpers/createApiClient.js b/helpers/createApiClient.js new file mode 100644 index 00000000..1f29c124 --- /dev/null +++ b/helpers/createApiClient.js @@ -0,0 +1,72 @@ +import feathers from '@feathersjs/feathers' +import socketio from '@feathersjs/socketio-client' +import io from 'socket.io-client' +import authentication from '@feathersjs/authentication-client' +import urlHelper from '~/helpers/urls' +import Cookie from 'cookie-universal' + +const authKey = 'feathers-jwt' + +const createSocket = (app) => { + let socket + const endpoint = urlHelper.buildEndpointURL(app.$env.API_HOST, { port: app.$env.API_PORT }) + if (process.env.ENV === 'production') { + socket = socketio(io(endpoint), { timeout: 20000 }) + if (process.server) { + setTimeout(() => { + // close server connection as content was delivered already after 30 seconds at latest + try { + socket.close() + } catch (err) { + console.log(err) + } + }, 30000) + } + } else { + socket = socketio(io(endpoint)) + } + + return socket +} + +let createApiClient = ({app, req, res}) => { + if (app.$api) return app.$api + + const cookies = Cookie(req, res) + const storageMapping = { + getItem: (key) => { + const res = cookies.get(key) + // console.log(`## STORAGE: getItem(${key})`, res) + return res + }, + setItem: (key, value, options) => { + const res = cookies.set(key, value, options) + // console.log(`## STORAGE: setItem(${key}, ${value}, ${options})`, res) + return res + }, + removeItem: (key) => { + const res = cookies.remove(key) + // console.log(`## STORAGE: removeItem(${key})`, res) + return res + }, + clear: () => { + const res = cookies.removeAll() + if (process.env.NODE_ENV === 'development') { + console.log(`## STORAGE: clear()`, res) + } + return res + } + } + + let api = feathers() + .configure(createSocket(app)) + .configure(authentication({ + storage: storageMapping, + storageKey: authKey, + cookie: authKey + })) + + return api +} + +export default createApiClient diff --git a/nuxt.config.js b/nuxt.config.js index 7bac7f5d..46131328 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -108,6 +108,7 @@ module.exports = { {src: '~/plugins/debug.js', ssr: false}, {src: '~/plugins/raven-client.js', ssr: false}, {src: '~/plugins/api.js'}, + {src: '~/plugins/init-feathers-vuex.js'}, {src: '~/plugins/vue-directives.js', ssr: false}, {src: '~/plugins/init-store-subscriptions.js', ssr: false}, {src: '~/plugins/keep-alive.js', ssr: false}, diff --git a/package.json b/package.json index 72d45427..bb258cec 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "express-healthcheck": "~0.1.0", "express-locale": "~1.0.5", "express-session": "~1.15.6", + "feathers-vuex": "^1.5.0", "font-awesome": "~4.7.0", "helmet": "^3.12.1", "i18n-iso-countries": "^3.7.8", diff --git a/plugins/api.js b/plugins/api.js index 4619ef7f..6c8a1a81 100644 --- a/plugins/api.js +++ b/plugins/api.js @@ -1,60 +1,15 @@ -import feathers from '@feathersjs/feathers' -import socketio from '@feathersjs/socketio-client' -import io from 'socket.io-client' -import authentication from '@feathersjs/authentication-client' -import urlHelper from '~/helpers/urls' +import feathersVuex from 'feathers-vuex' import Vue from 'vue' +import Vuex from 'vuex' +import createApiClient from '../helpers/createApiClient' -export default ({app, store, redirect, router}) => { - const authKey = 'feathers-jwt' - const endpoint = urlHelper.buildEndpointURL(app.$env.API_HOST, { port: app.$env.API_PORT }) - const storage = { - getItem: (key) => { - const res = app.$cookies.get(key) - // console.log(`## STORAGE: getItem(${key})`, res) - return res - }, - setItem: (key, value, options) => { - const res = app.$cookies.set(key, value, options) - // console.log(`## STORAGE: setItem(${key}, ${value}, ${options})`, res) - return res - }, - removeItem: (key) => { - const res = app.$cookies.remove(key) - // console.log(`## STORAGE: removeItem(${key})`, res) - return res - }, - clear: () => { - const res = app.$cookies.removeAll() - if (app.$env.NODE_ENV === 'development') { - console.log(`## STORAGE: clear()`, res) - } - return res - } - } +export default ({app, store, redirect, router, req, res}) => { + const api = createApiClient({app, req, res}) + const { FeathersVuex } = feathersVuex(api, { idField: '_id' }) - const socket = io(endpoint) + Vue.use(FeathersVuex) + Vue.use(Vuex) - if (process.server) { - setTimeout(() => { - // close server connection as content was delivered already after 30 seconds at latest - try { - socket.close() - } catch (err) { - app.error(err) - } - }, 30000) - } - - let api = feathers() - .configure(socketio(socket, { - timeout: 20000 - })) - .configure(authentication({ - storage: storage, - storageKey: authKey, - cookie: authKey - })) api.hooks({ before: { all: [ @@ -73,7 +28,7 @@ export default ({app, store, redirect, router}) => { if (app.$env.NODE_ENV === 'development') { console.log('####################') console.error(ctx.error) - console.info('JWT TOKEN: ', app.$cookies.get(authKey)) + // console.info('JWT TOKEN: ', app.$cookies.get(authKey)) console.info('path', ctx.path) console.info('service', ctx.service) console.info('method', ctx.method) @@ -103,7 +58,6 @@ export default ({app, store, redirect, router}) => { // fix issues where we could not log in // when at development await api.passport.logout() - storage.removeItem(authKey) } let user = null const response = await api.authenticate(options) @@ -120,11 +74,6 @@ export default ({app, store, redirect, router}) => { api.channel('authenticated').join(connection) }) - /** - * @deprecated - */ - api.authKey = authKey - // make the api accessible inside vue components Vue.use({ install (Vue) { diff --git a/plugins/init-feathers-vuex.js b/plugins/init-feathers-vuex.js new file mode 100644 index 00000000..4ef7fd81 --- /dev/null +++ b/plugins/init-feathers-vuex.js @@ -0,0 +1,19 @@ +import createApiClient from '../helpers/createApiClient' + +const requireModule = require.context( + // The relative path holding the service modules + '../store/services', + // Whether to look in subfolders + false, + // Only include .js files (prevents duplicate imports) + /.js$/ +) + +export default async ({app, store, req, res}) => { + const feathersClient = createApiClient({app, req, res}) + + const servicePlugins = requireModule.keys().map(modulePath => requireModule(modulePath).default(feathersClient)) + servicePlugins.forEach((servicePlugin) => { + servicePlugin(store) + }) +} diff --git a/store/init-feathers-vuex.js b/store/init-feathers-vuex.js new file mode 100644 index 00000000..4ce4bb78 --- /dev/null +++ b/store/init-feathers-vuex.js @@ -0,0 +1,13 @@ +import { initAuth } from 'feathers-vuex' + +export const actions = { + nuxtServerInit ({ commit, dispatch }, { req }) { + return initAuth({ + commit, + dispatch, + req, + moduleName: 'auth', + cookieName: 'feathers-jwt' + }) + } +} diff --git a/store/services/users.js b/store/services/users.js new file mode 100644 index 00000000..ae200954 --- /dev/null +++ b/store/services/users.js @@ -0,0 +1,11 @@ +import feathersVuex from 'feathers-vuex' + +let servicePlugin = (feathersClient) => { + const { service } = feathersVuex(feathersClient, { idField: '_id' }) + const servicePath = 'users' + const servicePlugin = service(servicePath, { + namespace: 'feathers-vuex-users' + }) + return servicePlugin +} +export default servicePlugin diff --git a/store/services/usersettings.js b/store/services/usersettings.js new file mode 100644 index 00000000..233538fc --- /dev/null +++ b/store/services/usersettings.js @@ -0,0 +1,11 @@ +import feathersVuex from 'feathers-vuex' + +let servicePlugin = (feathersClient) => { + const { service } = feathersVuex(feathersClient, { idField: '_id' }) + const servicePath = 'usersettings' + const servicePlugin = service(servicePath, { + namespace: 'feathers-vuex-usersettings' + }) + return servicePlugin +} +export default servicePlugin diff --git a/yarn.lock b/yarn.lock index 420914e2..54769966 100644 --- a/yarn.lock +++ b/yarn.lock @@ -706,6 +706,12 @@ dependencies: debug "^4.0.0" +"@feathersjs/errors@^3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@feathersjs/errors/-/errors-3.3.4.tgz#7dd591626ba57d1e8074464235b4d23906833ca7" + dependencies: + debug "^4.0.0" + "@feathersjs/feathers@3.2.2", "@feathersjs/feathers@^3.1.7": version "3.2.2" resolved "https://registry.yarnpkg.com/@feathersjs/feathers/-/feathers-3.2.2.tgz#026fe3c8a23ad0557d521a533b727b2f209c5918" @@ -3582,7 +3588,7 @@ debug@=3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0: +debug@^3.0.1, debug@^3.1.0, debug@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.5.tgz#c2418fbfd7a29f4d4f70ff4cea604d4b64c46407" dependencies: @@ -3602,6 +3608,10 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" +deep-diff@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26" + deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -4621,6 +4631,10 @@ falafel@^2.1.0: isarray "0.0.1" object-keys "^1.0.6" +fast-copy@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-1.2.2.tgz#b15cf14d767f07c22cd5ffbd6cc615c12d206b7e" + fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" @@ -4657,6 +4671,23 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" +feathers-vuex@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/feathers-vuex/-/feathers-vuex-1.6.2.tgz#08361839e6ffbb19ec9b56085f56fdec20653e2e" + dependencies: + "@feathersjs/commons" "^3.0.1" + "@feathersjs/errors" "^3.3.4" + debug "^3.2.5" + deep-diff "^1.0.2" + fast-copy "^1.2.2" + inflection "^1.12.0" + jwt-decode "^2.2.0" + lodash.isobject "^3.0.2" + lodash.merge "^4.6.1" + lodash.trim "^4.5.1" + serialize-error "^2.1.0" + sift "^6.0.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -5742,6 +5773,10 @@ indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" +inflection@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -6541,7 +6576,7 @@ jss@^9.8.1: symbol-observable "^1.1.0" warning "^3.0.0" -jwt-decode@^2.1.0: +jwt-decode@^2.1.0, jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -6653,7 +6688,7 @@ levn@^0.3.0, levn@~0.3.0: dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" - + linkifyjs@^2.1.7: version "2.1.7" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.7.tgz#e5d68d2ae30b9c055e1d74cc40f9a31d3abb4012" @@ -6794,6 +6829,10 @@ lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -6806,7 +6845,7 @@ lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" -lodash.merge@^4.6.0: +lodash.merge@^4.6.0, lodash.merge@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54" @@ -6835,11 +6874,15 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" +lodash.trim@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/lodash.trim/-/lodash.trim-4.5.1.tgz#36425e7ee90be4aa5e27bcebb85b7d11ea47aa57" + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.11, lodash@~4.17.4: +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.11: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -8865,16 +8908,16 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.6.2: +prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: loose-envify "^1.3.1" object-assign "^4.1.1" -protocol-buffers-schema@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-2.2.0.tgz#d29c6cd73fb655978fb6989691180db844119f61" +protocol-buffers-schema@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz#00434f608b4e8df54c59e070efeefc37fb4bb859" proxy-addr@~2.0.3: version "2.0.4" @@ -9177,7 +9220,7 @@ react-dev-utils@^5.0.0: strip-ansi "3.0.1" text-table "0.2.0" -react-dom@^16.4.2: +react-dom@^16.2.0, react-dom@^16.4.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" dependencies: @@ -9185,7 +9228,7 @@ react-dom@^16.4.2: object-assign "^4.1.1" prop-types "^15.6.2" schedule "^0.5.0" - + react-error-overlay@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" @@ -9215,15 +9258,6 @@ react@^16.2.0, react@^16.4.2: prop-types "^15.6.2" schedule "^0.5.0" -react@^16.4.2: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - schedule "^0.5.0" - read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -10087,6 +10121,10 @@ shuffle-seed@^1.1.6: dependencies: seedrandom "^2.4.2" +sift@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/sift/-/sift-6.0.0.tgz#f93a778e5cbf05a5024ebc391e6b32511a6d1f82" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"