├── .editorconfig ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cypress.json ├── dist ├── index.cjs.js ├── index.cjs.js.map ├── index.esm.js └── index.esm.js.map ├── package-lock.json ├── package.json ├── src ├── Guard.js ├── Guards │ ├── BeforeEach.js │ ├── BeforeEnter.js │ └── BeforeUpdate.js └── index.js └── test ├── app ├── Cypress.js ├── Logger.js ├── apps │ ├── before-each-basic │ │ ├── app.js │ │ ├── guards │ │ │ ├── GuardA.js │ │ │ └── GuardB.js │ │ ├── index.html │ │ └── views │ │ │ ├── App.vue │ │ │ ├── Foo.vue │ │ │ └── Logs.vue │ ├── before-enter-basic │ │ ├── app.js │ │ ├── guards │ │ │ ├── GuardA.js │ │ │ ├── GuardB.js │ │ │ ├── GuardC.js │ │ │ └── GuardD.js │ │ ├── index.html │ │ └── views │ │ │ ├── App.vue │ │ │ ├── Bar.vue │ │ │ ├── Baz.vue │ │ │ ├── Foo.vue │ │ │ └── Logs.vue │ └── before-update-basic │ │ ├── app.js │ │ ├── guards │ │ └── GuardA.js │ │ ├── index.html │ │ └── views │ │ ├── App.vue │ │ ├── Bar.vue │ │ └── Foo.vue ├── components │ └── Logs.vue └── index.html └── e2e ├── before-each-basic.js ├── before-enter-basic.js └── before-update-basic.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [Makefile] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Misc 2 | .DS_Store 3 | 4 | # App 5 | node_modules 6 | 7 | # Test 8 | test/.parcel 9 | 10 | # Cypress 11 | cypress 12 | 13 | # Parcel 14 | .parcel-cache 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Antoine Bellion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_IMG=cypress/browsers:node14.0.0-chrome84 2 | 3 | install: 4 | @docker run --rm -i -t -w /app -v ${PWD}:/app ${DOCKER_IMG} npm install 5 | 6 | cli: 7 | @docker run --rm -i -t -w /app -v ${PWD}:/app ${DOCKER_IMG} bash 8 | 9 | serve: 10 | @docker run --rm -i -t -w /app -v ${PWD}:/app -p 8080:8080 ${DOCKER_IMG} npx parcel serve --port 8080 --dist-dir test/.parcel --cache-dir test/.parcel test/app/index.html 11 | 12 | build: 13 | @docker run --rm -i -t -w /app -v ${PWD}:/app ${DOCKER_IMG} npx parcel build 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛡 Vue Router Shield 2 | 3 | Looking for a clean and elegent way to define navigation guards in your Vue.js application ? This library is made for you ! Whether you want to authenticate your users across your application, or just fetch some data before entering a view, this is a perfect fit. 4 | 5 | **How does it differ from built in navigation guards ?** 6 | 7 | | Feature | Vue Router | Vue Router Shield | 8 | |---------------------------|------------|-------------------| 9 | | Route based before enter | ✅ | ✅ | 10 | | Route based before update | ❌ | ✅ | 11 | | Route based before each | ❌ | ✅ | 12 | 13 | ## Installation 🔽 14 | 15 | > Note : compatible with Vue.js V2 and V3 ! 16 | 17 | ```bash 18 | npm install vue-router-shield 19 | # or 20 | yarn add vue-router-shield 21 | ``` 22 | 23 | ```js 24 | import VueRouterShield from 'vue-router-shield' 25 | 26 | Vue.use(VueRouterShield, { 27 | router // A router instance must be given 28 | }) 29 | ``` 30 | 31 | ## Usage 🔽 32 | 33 | Guards are defined at the route level, and can be executed before entering the route, before the route is updated, or both : you decide. Let's create a guard that will be called `BeforeEach` navigations and log a greeting message : 34 | 35 | ```js 36 | import { BeforeEach } from 'vue-router-shield' 37 | 38 | export default BeforeEach((to, from, next) => { 39 | console.log('Hello !') 40 | 41 | next() 42 | }) 43 | ``` 44 | 45 | Now, this guard must be bound to the route on which we want it to be executed. Of course, we can list as many as we want, and they'll all be called one after the other : 46 | 47 | ```js 48 | { 49 | routes: [ 50 | { 51 | path: '/', 52 | component: {}, 53 | meta: { guard: [SayHello] } 54 | } 55 | ] 56 | } 57 | ``` 58 | 59 | As said above, the guards can be executed in three different ways : 60 | 61 | ```js 62 | // BeforeEach guards are executed when the route they're bound to is entered or updated 63 | import { BeforeEach } from 'vue-router-shield' 64 | 65 | export default BeforeEach((to, from, next) => { 66 | next() 67 | }) 68 | 69 | // BeforeUpdate guards are executed only when the route they're bound to is updated 70 | import { BeforeUpdate } from 'vue-router-shield' 71 | 72 | export default BeforeUpdate((to, from, next) => { 73 | next() 74 | }) 75 | 76 | // BeforeEnter guards are executed only when the route they're bound to is entered 77 | import { BeforeEnter } from 'vue-router-shield' 78 | 79 | export default BeforeEnter((to, from, next) => { 80 | next() 81 | }) 82 | ``` 83 | 84 | They are called with three arguments : the route we're going `to`, the route we're coming `from`, and a `next` function that must be called in order to validate the navigation. If you want to abort the navigation or create a redirect, either pass `false` or a route location. 85 | 86 | ## Configuration 🔽 87 | 88 | Instead of `guard` as the name of the meta field the guards are taken from, you can choose another one when initializing the library : 89 | 90 | ```js 91 | import VueRouterShield from 'vue-router-shield' 92 | 93 | Vue.use(VueRouterShield, { 94 | router, 95 | guard: 'middleware' 96 | }) 97 | ``` 98 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://host.docker.internal:8080", 3 | "integrationFolder": "test/e2e" 4 | } 5 | -------------------------------------------------------------------------------- /dist/index.cjs.js: -------------------------------------------------------------------------------- 1 | function e(e,t,r,o){Object.defineProperty(e,t,{get:r,set:o,enumerable:!0,configurable:!0})}var t;t=module.exports,Object.defineProperty(t,"__esModule",{value:!0,configurable:!0}),e(module.exports,"Settings",(()=>d)),e(module.exports,"default",(()=>h)),e(module.exports,"BeforeEach",(()=>a)),e(module.exports,"BeforeEnter",(()=>n)),e(module.exports,"BeforeUpdate",(()=>u));class r{constructor(e,t){this.request=e,this.next=t}async handle(){const e=this.chain();for(let t of e){const e=await this.run(t);if(void 0!==e)return this.next(e)}return this.next()}run(e){return new Promise((t=>{e(this.request.to,this.request.from,t)}))}chain(){const e=new Set;for(let t of this.request.to.matched)t.meta.hasOwnProperty(d.guard)&&t.meta[d.guard].forEach(Set.prototype.add,e);return Array.from(e)}}var o=(e,t,o)=>{new r({to:e,from:t},o).handle()},a=e=>(t,r,o)=>{e(t,r,o)},n=e=>{let t=null;const r=(o,a,n)=>{let u=!1;if(t&&t.from.path===a.path&&t.to.matched[t.to.matched.length-1].path!==o.matched[o.matched.length-1].path&&(u=!0),t=u?null:{to:o,from:a},u)return n();for(let e in a.matched)if(a.matched[e].meta.hasOwnProperty(d.guard)&&a.matched[e].meta[d.guard].includes(r)&&a.matched[e]===o.matched[e])return n();e(o,a,n)};return r},u=e=>{const t=(r,o,a)=>{for(let n in o.matched)if(o.matched[n].meta.hasOwnProperty(d.guard)&&o.matched[n].meta[d.guard].includes(t)&&o.matched[n]===r.matched[n])return e(r,o,a);a()};return t};const d={guard:"guard"};function h(e,t){if(!0!==Boolean(t)||!0!==t.hasOwnProperty("router"))throw new Error("A Vue Router instance must be given to vue-router-shield");t.hasOwnProperty("guard")&&(d.guard=t.guard),t.router.beforeEach(o)} 2 | //# sourceMappingURL=index.cjs.js.map 3 | -------------------------------------------------------------------------------- /dist/index.cjs.js.map: -------------------------------------------------------------------------------- 1 | {"mappings":"0XAEMA,cACSC,EAASC,GACpBC,KAAKF,QAAUA,EACfE,KAAKD,KAAOA,EAMXE,eAED,MAAMC,EAAQF,KAAKE,QAEnB,IAAK,IAAIC,KAASD,EAAO,CACvB,MAAME,QAAeJ,KAAKK,IAAIF,GAE9B,QAAeG,IAAXF,EACF,OAAOJ,KAAKD,KAAKK,GAIrB,OAAOJ,KAAKD,OAQdM,IAAKF,GACH,OAAO,IAAII,SAASC,IAClBL,EAAMH,KAAKF,QAAQW,GAAIT,KAAKF,QAAQY,KAAMF,MAQ9CN,QACE,MAAMA,EAAQ,IAAIS,IAElB,IAAK,IAAIC,KAASZ,KAAKF,QAAQW,GAAGI,QAC5BD,EAAME,KAAKC,eAAeC,EAASb,QACrCS,EAAME,KAAKE,EAASb,OAAOc,QAAQN,IAAIO,UAAUC,IAAKjB,GAI1D,OAAOkB,MAAMV,KAAKR,QAItBmB,EAEC,CAFeZ,EAAIC,EAAMX,KACxB,IAAIF,EAAM,IAAEY,OAAIC,GAAQX,GAAMuB,UCvDhCC,EAAgBpB,GACR,CAAEM,EAAIC,EAAMX,KAChBI,EAAMM,EAAIC,EAAMX,ICApByB,EAAgBrB,IACd,IAAIsB,EAAO,KAEX,MAAMC,EAAO,CAAIjB,EAAIC,EAAMX,KACzB,IAAI4B,GAAO,EAYX,GAVIF,GAAQA,EAAKf,KAAKkB,OAASlB,EAAKkB,MAAQH,EAAKhB,GAAGI,QAAQY,EAAKhB,GAAGI,QAAQgB,OAAS,GAAGD,OAASnB,EAAGI,QAAQJ,EAAGI,QAAQgB,OAAS,GAAGD,OACjID,GAAO,GAIPF,EADEE,EACK,KAEA,IAAElB,OAAIC,GAGXiB,EACF,OAAO5B,IAGT,IAAK,IAAI+B,KAASpB,EAAKG,QACrB,GAAIH,EAAKG,QAAQiB,GAAOhB,KAAKC,eAAeC,EAASb,QAAUO,EAAKG,QAAQiB,GAAOhB,KAAKE,EAASb,OAAO4B,SAASL,IAAYhB,EAAKG,QAAQiB,KAAWrB,EAAGI,QAAQiB,GAC9J,OAAO/B,IAIXI,EAAMM,EAAIC,EAAMX,IAGlB,OAAO2B,GC7BTM,EAAgB7B,IACd,MAAMuB,EAAO,CAAIjB,EAAIC,EAAMX,KACzB,IAAK,IAAI+B,KAASpB,EAAKG,QACrB,GAAIH,EAAKG,QAAQiB,GAAOhB,KAAKC,eAAeC,EAASb,QAAUO,EAAKG,QAAQiB,GAAOhB,KAAKE,EAASb,OAAO4B,SAASL,IAAYhB,EAAKG,QAAQiB,KAAWrB,EAAGI,QAAQiB,GAC9J,OAAO3B,EAAMM,EAAIC,EAAMX,GAI3BA,KAGF,OAAO2B,GCPF,MAAMV,EAAW,CACtBb,MAAO,oBAGuB8B,EAAKC,GACnC,IAAyB,IAArBC,QAAQD,KAA0D,IAArCA,EAAQnB,eAAe,UACtD,MAAM,IAAIqB,MAAM,4DAGdF,EAAQnB,eAAe,WACzBC,EAASb,MAAQ+B,EAAQ/B,OAG3B+B,EAAQG,OAAOC,WAAWjB","sources":["src/Guard.js","src/Guards/BeforeEach.js","src/Guards/BeforeEnter.js","src/Guards/BeforeUpdate.js","src/index.js"],"sourcesContent":["import { Settings } from './index'\n\nclass Guard {\n constructor (request, next) {\n this.request = request\n this.next = next\n }\n\n /**\n * Handle the guards\n * @return mixed\n */\n async handle () {\n const chain = this.chain()\n\n for (let guard of chain) {\n const result = await this.run(guard)\n\n if (result !== undefined) {\n return this.next(result)\n }\n }\n\n return this.next()\n }\n\n /**\n * Run a guard\n * @param function guard\n * @return promise\n */\n run (guard) {\n return new Promise((resolve) => {\n guard(this.request.to, this.request.from, resolve)\n })\n }\n\n /**\n * Build the guards chain\n * @return array\n */\n chain () {\n const chain = new Set()\n\n for (let route of this.request.to.matched) {\n if (route.meta.hasOwnProperty(Settings.guard)) {\n route.meta[Settings.guard].forEach(Set.prototype.add, chain)\n }\n }\n\n return Array.from(chain)\n }\n}\n\nexport default (to, from, next) => {\n new Guard({ to, from }, next).handle()\n}\n","export default (guard) => {\n return (to, from, next) => {\n guard(to, from, next)\n }\n}\n","import { Settings } from '../index'\n\nexport default (guard) => {\n let last = null\n\n const wrapper = (to, from, next) => {\n let skip = false\n\n if (last && last.from.path === from.path && last.to.matched[last.to.matched.length - 1].path !== to.matched[to.matched.length - 1].path) {\n skip = true\n }\n\n if (skip) {\n last = null\n } else {\n last = { to, from }\n }\n\n if (skip) {\n return next()\n }\n\n for (let index in from.matched) {\n if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) {\n return next()\n }\n }\n\n guard(to, from, next)\n }\n\n return wrapper\n}\n","import { Settings } from '../index'\n\nexport default (guard) => {\n const wrapper = (to, from, next) => {\n for (let index in from.matched) {\n if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) {\n return guard(to, from, next)\n }\n }\n\n next()\n }\n\n return wrapper\n}\n","import Guard from './Guard'\n\nexport { default as BeforeEach } from './Guards/BeforeEach'\nexport { default as BeforeEnter } from './Guards/BeforeEnter'\nexport { default as BeforeUpdate } from './Guards/BeforeUpdate'\n\nexport const Settings = {\n guard: 'guard'\n}\n\nexport default function install(vue, options) {\n if (Boolean(options) !== true || options.hasOwnProperty('router') !== true) {\n throw new Error('A Vue Router instance must be given to vue-router-shield')\n }\n\n if (options.hasOwnProperty('guard')) {\n Settings.guard = options.guard\n }\n\n options.router.beforeEach(Guard)\n}\n"],"names":["$93b5ba20b7c6407f$var$Guard","request","next","this","async","chain","guard","result","run","undefined","Promise","resolve","to","from","Set","route","matched","meta","hasOwnProperty","$6dbc217b010a975b$export$c72f6eaae7b9adff","forEach","prototype","add","Array","$93b5ba20b7c6407f$export$2e2bcd8739ae039","handle","$4dc324b33f6b41f4$export$2e2bcd8739ae039","$fdff557f561d4944$export$2e2bcd8739ae039","last","wrapper","skip","path","length","index","includes","$9d1ec6dcc9074941$export$2e2bcd8739ae039","vue","options","Boolean","Error","router","beforeEach"],"version":3,"file":"index.cjs.js.map"} -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | class t{constructor(t,e){this.request=t,this.next=e}async handle(){const t=this.chain();for(let e of t){const t=await this.run(e);if(void 0!==t)return this.next(t)}return this.next()}run(t){return new Promise((e=>{t(this.request.to,this.request.from,e)}))}chain(){const t=new Set;for(let e of this.request.to.matched)e.meta.hasOwnProperty(o.guard)&&e.meta[o.guard].forEach(Set.prototype.add,t);return Array.from(t)}}var e=(e,r,a)=>{new t({to:e,from:r},a).handle()},r=t=>(e,r,a)=>{t(e,r,a)},a=t=>{let e=null;const r=(a,n,h)=>{let s=!1;if(e&&e.from.path===n.path&&e.to.matched[e.to.matched.length-1].path!==a.matched[a.matched.length-1].path&&(s=!0),e=s?null:{to:a,from:n},s)return h();for(let t in n.matched)if(n.matched[t].meta.hasOwnProperty(o.guard)&&n.matched[t].meta[o.guard].includes(r)&&n.matched[t]===a.matched[t])return h();t(a,n,h)};return r},n=t=>{const e=(r,a,n)=>{for(let h in a.matched)if(a.matched[h].meta.hasOwnProperty(o.guard)&&a.matched[h].meta[o.guard].includes(e)&&a.matched[h]===r.matched[h])return t(r,a,n);n()};return e};const o={guard:"guard"};function h(t,r){if(!0!==Boolean(r)||!0!==r.hasOwnProperty("router"))throw new Error("A Vue Router instance must be given to vue-router-shield");r.hasOwnProperty("guard")&&(o.guard=r.guard),r.router.beforeEach(e)}export{o as Settings,h as default,r as BeforeEach,a as BeforeEnter,n as BeforeUpdate}; 2 | //# sourceMappingURL=index.esm.js.map 3 | -------------------------------------------------------------------------------- /dist/index.esm.js.map: -------------------------------------------------------------------------------- 1 | {"mappings":"MAEMA,cACSC,EAASC,GACpBC,KAAKF,QAAUA,EACfE,KAAKD,KAAOA,EAMXE,eAED,MAAMC,EAAQF,KAAKE,QAEnB,IAAK,IAAIC,KAASD,EAAO,CACvB,MAAME,QAAeJ,KAAKK,IAAIF,GAE9B,QAAeG,IAAXF,EACF,OAAOJ,KAAKD,KAAKK,GAIrB,OAAOJ,KAAKD,OAQdM,IAAKF,GACH,OAAO,IAAII,SAASC,IAClBL,EAAMH,KAAKF,QAAQW,GAAIT,KAAKF,QAAQY,KAAMF,MAQ9CN,QACE,MAAMA,EAAQ,IAAIS,IAElB,IAAK,IAAIC,KAASZ,KAAKF,QAAQW,GAAGI,QAC5BD,EAAME,KAAKC,eAAeC,EAASb,QACrCS,EAAME,KAAKE,EAASb,OAAOc,QAAQN,IAAIO,UAAUC,IAAKjB,GAI1D,OAAOkB,MAAMV,KAAKR,QAItBmB,EAEC,CAFeZ,EAAIC,EAAMX,KACxB,IAAIF,EAAM,IAAEY,OAAIC,GAAQX,GAAMuB,UCvDhCC,EAAgBpB,GACR,CAAEM,EAAIC,EAAMX,KAChBI,EAAMM,EAAIC,EAAMX,ICApByB,EAAgBrB,IACd,IAAIsB,EAAO,KAEX,MAAMC,EAAO,CAAIjB,EAAIC,EAAMX,KACzB,IAAI4B,GAAO,EAYX,GAVIF,GAAQA,EAAKf,KAAKkB,OAASlB,EAAKkB,MAAQH,EAAKhB,GAAGI,QAAQY,EAAKhB,GAAGI,QAAQgB,OAAS,GAAGD,OAASnB,EAAGI,QAAQJ,EAAGI,QAAQgB,OAAS,GAAGD,OACjID,GAAO,GAIPF,EADEE,EACK,KAEA,IAAElB,OAAIC,GAGXiB,EACF,OAAO5B,IAGT,IAAK,IAAI+B,KAASpB,EAAKG,QACrB,GAAIH,EAAKG,QAAQiB,GAAOhB,KAAKC,eAAeC,EAASb,QAAUO,EAAKG,QAAQiB,GAAOhB,KAAKE,EAASb,OAAO4B,SAASL,IAAYhB,EAAKG,QAAQiB,KAAWrB,EAAGI,QAAQiB,GAC9J,OAAO/B,IAIXI,EAAMM,EAAIC,EAAMX,IAGlB,OAAO2B,GC7BTM,EAAgB7B,IACd,MAAMuB,EAAO,CAAIjB,EAAIC,EAAMX,KACzB,IAAK,IAAI+B,KAASpB,EAAKG,QACrB,GAAIH,EAAKG,QAAQiB,GAAOhB,KAAKC,eAAeC,EAASb,QAAUO,EAAKG,QAAQiB,GAAOhB,KAAKE,EAASb,OAAO4B,SAASL,IAAYhB,EAAKG,QAAQiB,KAAWrB,EAAGI,QAAQiB,GAC9J,OAAO3B,EAAMM,EAAIC,EAAMX,GAI3BA,KAGF,OAAO2B,GCPF,MAAMV,EAAW,CACtBb,MAAO,oBAGuB8B,EAAKC,GACnC,IAAyB,IAArBC,QAAQD,KAA0D,IAArCA,EAAQnB,eAAe,UACtD,MAAM,IAAIqB,MAAM,4DAGdF,EAAQnB,eAAe,WACzBC,EAASb,MAAQ+B,EAAQ/B,OAG3B+B,EAAQG,OAAOC,WAAWjB","sources":["src/Guard.js","src/Guards/BeforeEach.js","src/Guards/BeforeEnter.js","src/Guards/BeforeUpdate.js","src/index.js"],"sourcesContent":["import { Settings } from './index'\n\nclass Guard {\n constructor (request, next) {\n this.request = request\n this.next = next\n }\n\n /**\n * Handle the guards\n * @return mixed\n */\n async handle () {\n const chain = this.chain()\n\n for (let guard of chain) {\n const result = await this.run(guard)\n\n if (result !== undefined) {\n return this.next(result)\n }\n }\n\n return this.next()\n }\n\n /**\n * Run a guard\n * @param function guard\n * @return promise\n */\n run (guard) {\n return new Promise((resolve) => {\n guard(this.request.to, this.request.from, resolve)\n })\n }\n\n /**\n * Build the guards chain\n * @return array\n */\n chain () {\n const chain = new Set()\n\n for (let route of this.request.to.matched) {\n if (route.meta.hasOwnProperty(Settings.guard)) {\n route.meta[Settings.guard].forEach(Set.prototype.add, chain)\n }\n }\n\n return Array.from(chain)\n }\n}\n\nexport default (to, from, next) => {\n new Guard({ to, from }, next).handle()\n}\n","export default (guard) => {\n return (to, from, next) => {\n guard(to, from, next)\n }\n}\n","import { Settings } from '../index'\n\nexport default (guard) => {\n let last = null\n\n const wrapper = (to, from, next) => {\n let skip = false\n\n if (last && last.from.path === from.path && last.to.matched[last.to.matched.length - 1].path !== to.matched[to.matched.length - 1].path) {\n skip = true\n }\n\n if (skip) {\n last = null\n } else {\n last = { to, from }\n }\n\n if (skip) {\n return next()\n }\n\n for (let index in from.matched) {\n if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) {\n return next()\n }\n }\n\n guard(to, from, next)\n }\n\n return wrapper\n}\n","import { Settings } from '../index'\n\nexport default (guard) => {\n const wrapper = (to, from, next) => {\n for (let index in from.matched) {\n if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) {\n return guard(to, from, next)\n }\n }\n\n next()\n }\n\n return wrapper\n}\n","import Guard from './Guard'\n\nexport { default as BeforeEach } from './Guards/BeforeEach'\nexport { default as BeforeEnter } from './Guards/BeforeEnter'\nexport { default as BeforeUpdate } from './Guards/BeforeUpdate'\n\nexport const Settings = {\n guard: 'guard'\n}\n\nexport default function install(vue, options) {\n if (Boolean(options) !== true || options.hasOwnProperty('router') !== true) {\n throw new Error('A Vue Router instance must be given to vue-router-shield')\n }\n\n if (options.hasOwnProperty('guard')) {\n Settings.guard = options.guard\n }\n\n options.router.beforeEach(Guard)\n}\n"],"names":["$4ab05e376f6c4396$var$Guard","request","next","this","async","chain","guard","result","run","undefined","Promise","resolve","to","from","Set","route","matched","meta","hasOwnProperty","$c7cd4a810d0ec338$export$c72f6eaae7b9adff","forEach","prototype","add","Array","$4ab05e376f6c4396$export$2e2bcd8739ae039","handle","$fbd1b217618515b7$export$2e2bcd8739ae039","$a4ae4a5461961f79$export$2e2bcd8739ae039","last","wrapper","skip","path","length","index","includes","$adf23e1305b55d55$export$2e2bcd8739ae039","vue","options","Boolean","Error","router","beforeEach"],"version":3,"file":"index.esm.js.map"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-router-shield", 3 | "version": "1.0.1", 4 | "description": "Vue Router Shield enhance the Vue.js router by adding new navigation guards capabilities", 5 | "source": "src/index.js", 6 | "main": "dist/index.cjs.js", 7 | "module": "dist/index.esm.js", 8 | "author": "Antoine Bellion", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/abellion/vue-router-shield" 13 | }, 14 | "devDependencies": { 15 | "@parcel/transformer-vue": "^2.0.1", 16 | "cypress": "^9.5.0", 17 | "parcel": "^2.0.1", 18 | "vue": "^3.2.31", 19 | "vue-router": "^4.0.12" 20 | }, 21 | "targets": { 22 | "main": { 23 | "optimize": true 24 | }, 25 | "module": { 26 | "optimize": true 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Guard.js: -------------------------------------------------------------------------------- 1 | import { Settings } from './index' 2 | 3 | class Guard { 4 | constructor (request, next) { 5 | this.request = request 6 | this.next = next 7 | } 8 | 9 | /** 10 | * Handle the guards 11 | * @return mixed 12 | */ 13 | async handle () { 14 | const chain = this.chain() 15 | 16 | for (let guard of chain) { 17 | const result = await this.run(guard) 18 | 19 | if (result !== undefined) { 20 | return this.next(result) 21 | } 22 | } 23 | 24 | return this.next() 25 | } 26 | 27 | /** 28 | * Run a guard 29 | * @param function guard 30 | * @return promise 31 | */ 32 | run (guard) { 33 | return new Promise((resolve) => { 34 | guard(this.request.to, this.request.from, resolve) 35 | }) 36 | } 37 | 38 | /** 39 | * Build the guards chain 40 | * @return array 41 | */ 42 | chain () { 43 | const chain = new Set() 44 | 45 | for (let route of this.request.to.matched) { 46 | if (route.meta.hasOwnProperty(Settings.guard)) { 47 | route.meta[Settings.guard].forEach(Set.prototype.add, chain) 48 | } 49 | } 50 | 51 | return Array.from(chain) 52 | } 53 | } 54 | 55 | export default (to, from, next) => { 56 | new Guard({ to, from }, next).handle() 57 | } 58 | -------------------------------------------------------------------------------- /src/Guards/BeforeEach.js: -------------------------------------------------------------------------------- 1 | export default (guard) => { 2 | return (to, from, next) => { 3 | guard(to, from, next) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Guards/BeforeEnter.js: -------------------------------------------------------------------------------- 1 | import { Settings } from '../index' 2 | 3 | export default (guard) => { 4 | let last = null 5 | 6 | const wrapper = (to, from, next) => { 7 | let skip = false 8 | 9 | if (last && last.from.path === from.path && last.to.matched[last.to.matched.length - 1].path !== to.matched[to.matched.length - 1].path) { 10 | skip = true 11 | } 12 | 13 | if (skip) { 14 | last = null 15 | } else { 16 | last = { to, from } 17 | } 18 | 19 | if (skip) { 20 | return next() 21 | } 22 | 23 | for (let index in from.matched) { 24 | if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) { 25 | return next() 26 | } 27 | } 28 | 29 | guard(to, from, next) 30 | } 31 | 32 | return wrapper 33 | } 34 | -------------------------------------------------------------------------------- /src/Guards/BeforeUpdate.js: -------------------------------------------------------------------------------- 1 | import { Settings } from '../index' 2 | 3 | export default (guard) => { 4 | const wrapper = (to, from, next) => { 5 | for (let index in from.matched) { 6 | if (from.matched[index].meta.hasOwnProperty(Settings.guard) && from.matched[index].meta[Settings.guard].includes(wrapper) && from.matched[index] === to.matched[index]) { 7 | return guard(to, from, next) 8 | } 9 | } 10 | 11 | next() 12 | } 13 | 14 | return wrapper 15 | } 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Guard from './Guard' 2 | 3 | export { default as BeforeEach } from './Guards/BeforeEach' 4 | export { default as BeforeEnter } from './Guards/BeforeEnter' 5 | export { default as BeforeUpdate } from './Guards/BeforeUpdate' 6 | 7 | export const Settings = { 8 | guard: 'guard' 9 | } 10 | 11 | export default function install(vue, options) { 12 | if (Boolean(options) !== true || options.hasOwnProperty('router') !== true) { 13 | throw new Error('A Vue Router instance must be given to vue-router-shield') 14 | } 15 | 16 | if (options.hasOwnProperty('guard')) { 17 | Settings.guard = options.guard 18 | } 19 | 20 | options.router.beforeEach(Guard) 21 | } 22 | -------------------------------------------------------------------------------- /test/app/Cypress.js: -------------------------------------------------------------------------------- 1 | export default function install(vue, options) { 2 | vue.directive('cypress-target', { 3 | mounted (el, { value }) { 4 | el.classList.add(`cy-target-${value}`) 5 | }, 6 | 7 | updated (el, { value }) { 8 | el.classList.add(`cy-target-${value}`) 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /test/app/Logger.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | export const logs = reactive([]) 4 | 5 | export function log(msg) { 6 | logs.push(msg) 7 | } 8 | -------------------------------------------------------------------------------- /test/app/apps/before-each-basic/app.js: -------------------------------------------------------------------------------- 1 | import * as Vue from 'vue' 2 | import * as VueRouter from 'vue-router' 3 | 4 | import guard from '../../../../src' 5 | import cypress from '../../Cypress' 6 | 7 | import App from './views/App.vue' 8 | import Foo from './views/Foo.vue' 9 | import Logs from './views/Logs.vue' 10 | 11 | import GuardA from './guards/GuardA' 12 | import GuardB from './guards/GuardB' 13 | 14 | const router = VueRouter.createRouter({ 15 | history: VueRouter.createWebHistory('/apps/before-each-basic'), 16 | routes: [ 17 | { 18 | path: '/foo', 19 | component: Foo, 20 | meta: { guard: [GuardA] }, 21 | children: [ 22 | { 23 | path: 'bar', 24 | component: Logs, 25 | meta: { guard: [GuardB] } 26 | } 27 | ] 28 | }, 29 | { 30 | path: '/:pathMatch(.*)*', 31 | redirect: '/foo' 32 | } 33 | ] 34 | }) 35 | 36 | const app = Vue.createApp(App) 37 | 38 | app.use(router) 39 | app.use(guard, { router }) 40 | app.use(cypress) 41 | app.mount('#app') 42 | -------------------------------------------------------------------------------- /test/app/apps/before-each-basic/guards/GuardA.js: -------------------------------------------------------------------------------- 1 | import * as Logger from '../../../Logger' 2 | import { BeforeEach } from '../../../../../src' 3 | 4 | export default BeforeEach((to, from, next) => { 5 | Logger.log('Guard A called') 6 | 7 | next() 8 | }) 9 | -------------------------------------------------------------------------------- /test/app/apps/before-each-basic/guards/GuardB.js: -------------------------------------------------------------------------------- 1 | import * as Logger from '../../../Logger' 2 | import { BeforeEach } from '../../../../../src' 3 | 4 | export default BeforeEach((to, from, next) => { 5 | Logger.log('Guard B called') 6 | 7 | next() 8 | }) 9 | -------------------------------------------------------------------------------- /test/app/apps/before-each-basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |{{ log }}
5 |