diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..7380491 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# vue-authenticate +`vue-authenticate` is easily configurable solution for *Vue.js* that provides local login/registration as well as Social login using Github, Facebook, Google and Twitter OAuth providers. + +The best part about this library is that it is not strictly coupled to one request handling library like `vue-resource`. You will be able to use it with different libraries. + +For now it is tested to work with `vue-resource` and `axios` (using `vue-axios` wrapper). + +This library was inspired by well known authentication library for Angular called [Satellizer](https://github.com/sahat/satellizer) developed by [Sahat Yalkabov](http://sahatyalkabov.com). They share almost identical configuration and API so you can easily switch from Angular to Vue.js project. + +## Instalation +`npm install vue-authenticate` + +## Usage +```javascript +import Vue from 'vue' +import VueResource from 'vue-resource' +import VueAuthenticate from '../src/index.js' + +Vue.use(VueRouter) +Vue.use(VueResource) + +Vue.use(VueAuthenticate, { + baseUrl: 'http://localhost:4000', + + providers: { + github: { + clientId: '91b3c6a5b8411640e1b3', + redirectUri: 'http://localhost:8080/auth/callback' + } + } +} +``` + +### Email & password login and registration +```javascript +new Vue({ + methods: { + login: function () { + this.$auth.login({ email, password }).then(function () { + // Execute application logic after successful login + }) + }, + + register: function () { + this.$auth.register({ name, email, password }).then(function () { + // Execute application logic after successful registration + }) + } + } +}) +``` + +### Social authentication + +```javascript +new Vue({ + methods: { + authenticate: function (provider) { + this.$auth.authenticate(provider).then(function () { + // Execute application logic after successful social authentication + }) + } + } +}) +``` + +```html + + + + +``` + +## License + +The MIT License (MIT) + +Copyright (c) 2017 Davor Grubelić + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..1eed825 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,33 @@ +var gulp = require('gulp'), + connect = require('gulp-connect') + webpack = require('webpack'), + gulpWebpack = require('gulp-webpack') + history = require('connect-history-api-fallback') + +gulp.task('compile', function () { + var webpackConfig = require('./test/webpack.config.js') + return gulp.src('./test/index.js') + .pipe(gulpWebpack(webpackConfig, webpack)) + .pipe(gulp.dest('./test')) + .pipe(connect.reload()) +}) + +gulp.task('server', function () { + connect.server({ + name: 'VueAuthentication', + root: './test', + base: 'test', + port: 8080, + livereload: true, + verbose: true, + middleware: function () { + return [history()] + } + }); +}) + +gulp.task('watch', function () { + gulp.watch(['./test/index.js', './src/**/*.js'], ['compile']) +}) + +gulp.task('dev', ['compile', 'server', 'watch']) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2343e42 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "vue-authenticate", + "version": "1.0.0", + "description": "Authentication library for Vue.js", + "main": "src/index.js", + "scripts": { + "dev": "parallelshell \"node-dev ./test/server.js\" \"gulp dev\"" + }, + "keywords": [ + "vue", + "vuejs", + "auth" + ], + "author": { + "name": "Davor Grubelic", + "email": "davor.grubelic@gmail.com", + "url": "https://dgrubelic.me" + }, + "repository": { + "type": "git", + "url": "https://github.com/dgrubelic/vue-authenticate.git" + }, + "license": "MIT", + "dependencies": { + "promise-polyfill": "^6.0.2", + "vue": "^2.2.5", + "vue-resource": "^1.2.1" + }, + "devDependencies": { + "axios": "^0.15.3", + "babel-core": "^6.24.0", + "babel-loader": "^6.4.1", + "body-parser": "^1.17.1", + "connect-history-api-fallback": "^1.3.0", + "cors": "^2.8.1", + "express": "^4.15.2", + "gulp": "^3.9.1", + "gulp-connect": "^5.0.0", + "gulp-webpack": "^1.5.0", + "node-dev": "^3.1.3", + "oauth": "^0.9.15", + "oauth-signature": "^1.3.1", + "parallelshell": "^2.0.0", + "request": "^2.81.0", + "unix-timestamp": "^0.2.0", + "vue-axios": "^2.0.1", + "vue-router": "^2.3.0", + "webpack": "^2.3.2", + "webpack-dev-server": "^2.4.2" + }, + "tags": [ + "auth", + "authentication", + "login", + "registration", + "jwt", + "token", + "vuejs", + "vue.js", + "github", + "facebook", + "google", + "twitter", + "oauth", + "oauth 1.0", + "oauth 2.0" + ] +} diff --git a/src/auth.js b/src/auth.js new file mode 100644 index 0000000..1961cf0 --- /dev/null +++ b/src/auth.js @@ -0,0 +1,260 @@ +import Vue from 'vue' +import Promise from './promise.js' +import { objectExtend, isString, isObject, isFunction, joinUrl, decodeBase64 } from './utils.js' +import defaultOptions from './options.js' +import StorageFactory from './storage.js' +import OAuth1 from './oauth/oauth1.js' +import OAuth2 from './oauth/oauth2.js' + +export default class VueAuthenticate { + constructor($http, overrideOptions) { + let options = objectExtend({}, defaultOptions) + options = objectExtend(options, overrideOptions) + let storage = StorageFactory(options) + + Object.defineProperties(this, { + $http: { + get() { + return $http + } + }, + + options: { + get() { + return options + } + }, + + storage: { + get() { + return storage + } + }, + + tokenName: { + get() { + if (this.options.tokenPrefix) { + return [this.options.tokenPrefix, this.options.tokenName].join('_') + } else { + return this.options.tokenName + } + } + } + }) + + // Setup request interceptors + if (this.options.requestInterceptor && isFunction(this.options.requestInterceptor)) { + this.options.requestInterceptor.call(this) + + if (this.options.responseInterceptor && isFunction(this.options.responseInterceptor)) { + this.options.responseInterceptor.call(this) + } + } else { + // By default, insert request interceptor for vue-resource + Vue.http.interceptors.push((request, next) => { + if (this.isAuthenticated()) { + request.headers.set('Authorization', [ + this.options.tokenType, this.getToken() + ].join(' ')) + } else { + request.headers.delete('Authorization') + } + + if (this.options.responseInterceptor && isFunction(this.options.responseInterceptor)) { + this.options.responseInterceptor.call(this) + } else { + next((response) => { + try { + var responseJson = JSON.parse(response[this.options.responseDataKey]) + if (responseJson[this.options.tokenName]) { + this.setToken(responseJson) + delete responseJson[this.options.tokenName] + } + } catch(e) {} + }) + } + }) + } + } + + /** + * Check if user is authenticated + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * @return {Boolean} + */ + isAuthenticated() { + let token = this.storage.getItem(this.tokenName) + + if (token) { // Token is present + if (token.split('.').length === 3) { // Token with a valid JWT format XXX.YYY.ZZZ + try { // Could be a valid JWT or an access token with the same format + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace('-', '+').replace('_', '/'); + const exp = JSON.parse(this.$window.atob(base64)).exp; + if (typeof exp === 'number') { // JWT with an optonal expiration claims + return Math.round(new Date().getTime() / 1000) < exp; + } + } catch (e) { + return true; // Pass: Non-JWT token that looks like JWT + } + } + return true; // Pass: All other tokens + } + return false + } + + /** + * Get token if user is authenticated + * @return {String} Authentication token + */ + getToken() { + return this.storage.getItem(this.tokenName) + } + + /** + * Set new authentication token + * @param {String|Object} token + */ + setToken(response) { + if (response[this.options.requestDataKey]) { + response = response[this.options.requestDataKey]; + } + + let token; + if (response.access_token) { + if (isObject(response.access_token) && isObject(response.access_token[this.options.requestDataKey])) { + response = response.access_token + } else if (isString(response.access_token)) { + token = response.access_token + } + } + + if (!token && response) { + token = response[this.options.tokenName] + } + + if (token) { + this.storage.setItem(this.tokenName, token) + } + } + + getPayload() { + const token = this.storage.getItem(this.tokenName); + + if (token && token.split('.').length === 3) { + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace('-', '+').replace('_', '/'); + return JSON.parse(decodeBase64(base64)); + } catch (e) { + console.log(e) + } + } + } + + /** + * Login user using email and password + * @param {Object} user User data + * @param {Object} requestOptions Request options + * @return {Promise} Request promise + */ + login(user, requestOptions) { + requestOptions = requestOptions || {} + requestOptions.url = requestOptions.url ? requestOptions.url : joinUrl(this.options.baseUrl, this.options.loginUrl) + requestOptions[this.options.requestDataKey] = user || requestOptions[this.options.requestDataKey] + requestOptions.method = requestOptions.method || 'POST' + requestOptions.withCredentials = requestOptions.withCredentials || this.options.withCredentials + + return this.$http(requestOptions).then((response) => { + this.setToken(response) + return response + }) + } + + /** + * Register new user + * @param {Object} user User data + * @param {Object} requestOptions Request options + * @return {Promise} Request promise + */ + register(user, requestOptions) { + requestOptions = requestOptions || {} + requestOptions.url = requestOptions.url ? requestOptions.url : joinUrl(this.options.baseUrl, this.options.registerUrl) + requestOptions[this.options.requestDataKey] = user || requestOptions[this.options.requestDataKey] + requestOptions.method = requestOptions.method || 'POST' + requestOptions.withCredentials = requestOptions.withCredentials || this.options.withCredentials + + return this.$http(requestOptions).then((response) => { + this.setToken(response) + return response + }) + } + + /** + * Logout current user + * @param {Object} requestOptions Logout request options object + * @return {Promise} Request promise + */ + logout(requestOptions) { + if (!this.isAuthenticated()) { + return Promise.reject(new Error('There is no currently authenticated user')) + } + + requestOptions = requestOptions || {} + requestOptions.url = requestOptions.logoutUrl || this.options.logoutUrl + + if (requestOptions.url) { + requestOptions.method = requestOptions.method || 'POST' + requestOptions.withCredentials = requestOptions.withCredentials || this.options.withCredentials + + return this.$http(requestOptions).then((response) => { + this.storage.removeItem(this.tokenName) + }) + } else { + this.storage.removeItem(this.tokenName) + return Promise.resolve(); + } + } + + /** + * Authenticate user using authentication provider + * + * @param {String} provider Provider name + * @param {Object} userData User data + * @param {Object} requestOptions Request options + * @return {Promise} Request promise + */ + authenticate(provider, userData, requestOptions) { + return new Promise((resolve, reject) => { + var providerConfig = this.options.providers[provider] + if (!providerConfig) { + return reject(new Error('Unknown provider')) + } + + let providerInstance; + switch (providerConfig.oauthType) { + case '1.0': + providerInstance = new OAuth1(this.$http, this.storage, providerConfig, this.options) + break + case '2.0': + providerInstance = new OAuth2(this.$http, this.storage, providerConfig, this.options) + break + default: + return reject(new Error('Invalid OAuth type')) + break + } + + return providerInstance.init(userData).then((response) => { + this.setToken(response) + if (this.isAuthenticated()) { + return resolve(response) + } else { + return reject(new Error('Authentication failed')) + } + }).catch(() => { + reject(new Error('Authentication error occurred')) + }) + }) + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..38c826b --- /dev/null +++ b/src/index.js @@ -0,0 +1,28 @@ +import './utils.js' +import Promise from './promise.js' +import VueAuthenticate from './auth.js' + +/** + * VueAuthenticate plugin + * @param {Object} Vue + */ +function plugin(Vue, options) { + if (plugin.installed) { + return; + } + + Object.defineProperties(Vue.prototype, { + $auth: { + get() { + // vue-resource module not found, throw error + if (!this.$http) { + throw new Error('vue-resource instance not found') + } + + return new VueAuthenticate(this.$http, options) + } + } + }); +} + +export default plugin; \ No newline at end of file diff --git a/src/oauth/oauth1.js b/src/oauth/oauth1.js new file mode 100644 index 0000000..db57f6c --- /dev/null +++ b/src/oauth/oauth1.js @@ -0,0 +1,109 @@ +import OAuthPopup from './popup.js' +import { objectExtend, isString, isObject, isFunction, joinUrl } from '../utils.js' + +const defaultProviderConfig = { + name: null, + url: null, + authorizationEndpoint: null, + scope: null, + scopePrefix: null, + scopeDelimiter: null, + redirectUri: null, + requiredUrlParams: null, + defaultUrlParams: null, + oauthType: '1.0', + popupOptions: { width: null, height: null } +} + +export default class OAuth { + constructor($http, storage, providerConfig, options) { + this.$http = $http + this.storage = storage + this.providerConfig = objectExtend({}, defaultProviderConfig) + this.providerConfig = objectExtend(this.providerConfig, providerConfig) + this.options = options + } + + /** + * Initialize OAuth1 process + * @param {Object} userData User data + * @return {Promise} + */ + init(userData) { + this.oauthPopup = new OAuthPopup('about:blank', this.providerConfig.name, this.providerConfig.popupOptions) + + if (window && !window['cordova']) { + this.oauthPopup.open(this.providerConfig.redirectUri, true) + } + + return this.getRequestToken().then((response) => { + return this.openPopup(response).then((popupResponse) => { + return this.exchangeForToken(popupResponse, userData) + }) + }) + } + + /** + * Get OAuth1 request token + * @return {Promise} + */ + getRequestToken() { + let requestOptions = {} + requestOptions.method = 'POST' + requestOptions[this.options.requestDataKey] = objectExtend({}, this.providerConfig) + requestOptions.withCredentials = this.options.withCredentials + if (this.options.baseUrl) { + requestOptions.url = joinUrl(this.options.baseUrl, this.providerConfig.url) + } else { + requestOptions.url = this.providerConfig.url + } + + return this.$http(requestOptions) + } + + /** + * Open OAuth1 popup + * @param {Object} response Response object containing request token + * @return {Promise} + */ + openPopup(response) { + const url = [this.providerConfig.authorizationEndpoint, this.buildQueryString(response[this.options.responseDataKey])].join('?'); + + this.oauthPopup.popup.location = url + if (window && window['cordova']) { + return this.oauthPopup.open(this.providerConfig.redirectUri) + } else { + return this.oauthPopup.pooling(this.providerConfig.redirectUri) + } + } + + /** + * Exchange token and token verifier for access token + * @param {Object} oauth OAuth data containing token and token verifier + * @param {Object} userData User data + * @return {Promise} + */ + exchangeForToken(oauth, userData) { + let payload = objectExtend({}, userData) + payload = objectExtend(payload, oauth) + let requestOptions = {} + requestOptions.method = 'POST' + requestOptions[this.options.requestDataKey] = payload + requestOptions.withCredentials = this.options.withCredentials + if (this.options.baseUrl) { + requestOptions.url = joinUrl(this.options.baseUrl, this.providerConfig.url) + } else { + requestOptions.url = this.providerConfig.url + } + return this.$http(requestOptions) + } + + buildQueryString(params) { + const parsedParams = []; + for (var key in params) { + let value = params[key] + parsedParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + } + return parsedParams.join('&'); + } +} \ No newline at end of file diff --git a/src/oauth/oauth2.js b/src/oauth/oauth2.js new file mode 100644 index 0000000..aa0d556 --- /dev/null +++ b/src/oauth/oauth2.js @@ -0,0 +1,154 @@ +import OAuthPopup from './popup.js' +import { camelCase, isFunction, isString, objectExtend, joinUrl } from '../utils.js' + +/** + * Default provider configuration + * @type {Object} + */ +const defaultProviderConfig = { + name: null, + url: null, + clientId: null, + authorizationEndpoint: null, + redirectUri: null, + scope: null, + scopePrefix: null, + scopeDelimiter: null, + state: null, + requiredUrlParams: null, + defaultUrlParams: ['response_type', 'client_id', 'redirect_uri'], + responseType: 'code', + responseParams: { + code: 'code', + clientId: 'clientId', + redirectUri: 'redirectUri' + }, + oauthType: '2.0', + popupOptions: { width: null, height: null } +} + +export default class OAuth2 { + constructor($http, storage, providerConfig, options) { + this.$http = $http + this.storage = storage + this.providerConfig = objectExtend({}, defaultProviderConfig) + this.providerConfig = objectExtend(this.providerConfig, providerConfig) + this.options = options + } + + init(userData) { + let stateName = this.providerConfig.name + if (isFunction(this.providerConfig.state)) { + this.storage.setItem(stateName, this.providerConfig.state()) + } else if (isString(this.providerConfig.state)) { + this.storage.setItem(stateName, this.providerConfig.state) + } + + let url = [this.providerConfig.authorizationEndpoint, this._stringifyRequestParams()].join('?') + + this.oauthPopup = new OAuthPopup(url, this.providerConfig.name, this.providerConfig.popupOptions) + + return new Promise((resolve, reject) => { + this.oauthPopup.open(this.providerConfig.redirectUri).then((response) => { + if (this.providerConfig.responseType === 'token' || !this.providerConfig.url) { + return resolve(response) + } + + if (response.state && response.state !== this.storage.getItem(stateName)) { + return reject(new Error('State parameter value does not match original OAuth request state value')) + } + + resolve(this.exchangeForToken(response, userData)) + }).catch((err) => { + reject(err) + }) + }) + } + + /** + * Exchange temporary oauth data for access token + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {[type]} oauth [description] + * @param {[type]} userData [description] + * @return {[type]} [description] + */ + exchangeForToken(oauth, userData) { + let payload = objectExtend({}, userData) + + for (let key in defaultProviderConfig.responseParams) { + let value = defaultProviderConfig[key] + + switch(key) { + case 'code': + payload[key] = oauth.code + break + case 'clientId': + payload[key] = this.providerConfig.clientId + break + case 'redirectUri': + payload[key] = this.providerConfig.redirectUri + break + default: + payload[key] = oauth[key] + } + } + + if (oauth.state) { + payload.state = oauth.state + } + + let exchangeTokenUrl + if (this.options.baseUrl) { + exchangeTokenUrl = joinUrl(this.options.baseUrl, this.providerConfig.url) + } else { + exchangeTokenUrl = this.providerConfig.url + } + + return this.$http.post(exchangeTokenUrl, payload, { + withCredentials: this.options.withCredentials + }) + } + + /** + * Stringify oauth params + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @return {String} + */ + _stringifyRequestParams() { + let keyValuePairs = [] + let paramCategories = ['defaultUrlParams', 'requiredUrlParams', 'optionalUrlParams'] + + paramCategories.forEach((categoryName) => { + if (!this.providerConfig[categoryName]) return + if (!Array.isArray(this.providerConfig[categoryName])) return + + this.providerConfig[categoryName].forEach((paramName) => { + let camelCaseParamName = camelCase(paramName) + let paramValue = isFunction(this.providerConfig[paramName]) ? this.providerConfig[paramName]() : this.providerConfig[camelCaseParamName] + + if (paramName === 'redirect_uri' && !paramValue) return + + if (paramName === 'state') { + const stateName = this.defaults.name + '_state'; + paramValue = encodeURIComponent(this.storage.getItem(stateName)); + } + if (paramName === 'scope' && Array.isArray(paramValue)) { + paramValue = paramValue.join(this.providerConfig.scopeDelimiter); + if (this.providerConfig.scopePrefix) { + paramValue = [this.providerConfig.scopePrefix, paramValue].join(this.providerConfig.scopeDelimiter); + } + } + + keyValuePairs.push([paramName, paramValue]) + }) + }) + + return keyValuePairs.map((param) => { + return param.join('=') + }).join('&') + } +} \ No newline at end of file diff --git a/src/oauth/popup.js b/src/oauth/popup.js new file mode 100644 index 0000000..a6a9fac --- /dev/null +++ b/src/oauth/popup.js @@ -0,0 +1,86 @@ +import Promise from '../promise.js' +import { objectExtend, parseQueryString, getFullUrlPath } from '../utils.js' + +/** + * OAuth2 popup management class + * + * @author Sahat Yalkabov + * @copyright Class mostly taken from https://github.com/sahat/satellizer + * and adjusted to fit vue-authenticate library + */ +export default class OAuthPopup { + constructor(url, name, popupOptions) { + this.popup = null + this.url = url + this.name = name + this.popupOptions = popupOptions + } + + open(redirectUri, skipPooling) { + try { + this.popup = window.open(this.url, this.name, this._stringifyOptions()) + if (this.popup && this.popup.focus) { + this.popup.focus() + } + + if (skipPooling) { + return Promise.resolve() + } else { + return this.pooling(redirectUri) + } + } catch(e) { + return Promise.reject(new Error('OAuth popup error occurred')) + } + } + + pooling(redirectUri) { + return new Promise((resolve, reject) => { + const redirectUriParser = document.createElement('a') + redirectUriParser.href = redirectUri + const redirectUriPath = getFullUrlPath(redirectUriParser) + + let poolingInterval = setInterval(() => { + if (!this.popup || this.popup.closed || this.popup.closed === undefined) { + clearInterval(poolingInterval) + poolingInterval = null + reject(new Error('Auth popup window closed')) + } + + try { + const popupWindowPath = getFullUrlPath(this.popup.location) + + if (popupWindowPath === redirectUriPath) { + if (this.popup.location.search || this.popup.location.hash) { + const query = parseQueryString(this.popup.location.search.substring(1).replace(/\/$/, '')); + const hash = parseQueryString(this.popup.location.hash.substring(1).replace(/[\/$]/, '')); + let params = objectExtend({}, query); + params = objectExtend(params, hash) + + if (params.error) { + reject(new Error(params.error)); + } else { + resolve(params); + } + } else { + reject(new Error('OAuth redirect has occurred but no query or hash parameters were found.')) + } + + clearInterval(poolingInterval) + poolingInterval = null + this.popup.close() + } + } catch(e) { + // Ignore DOMException: Blocked a frame with origin from accessing a cross-origin frame. + } + }, 250) + }) + } + + _stringifyOptions() { + let options = [] + for (var optionKey in this.popupOptions) { + options.push(`${optionKey}=${this.popupOptions[optionKey]}`) + } + return options.join(',') + } +} \ No newline at end of file diff --git a/src/options.js b/src/options.js new file mode 100644 index 0000000..70ade0f --- /dev/null +++ b/src/options.js @@ -0,0 +1,70 @@ +/** + * @author Sahat Yalkabov + * @copyright Configuration taken from https://github.com/sahat/satellizer + * and extended to fit vue-authenticate library + */ +export default { + baseUrl: null, + tokenName: 'token', + tokenPrefix: 'vueauth', + tokenHeader: 'Authorization', + tokenType: 'Bearer', + loginUrl: '/auth/login', + registerUrl: '/auth/register', + logoutUrl: null, + storageType: 'localStorage', + storageNamespace: 'vue-authenticate', + requestDataKey: 'body', + responseDataKey: 'body', + + providers: { + facebook: { + name: 'facebook', + url: '/auth/facebook', + authorizationEndpoint: 'https://www.facebook.com/v2.5/dialog/oauth', + redirectUri: null, + requiredUrlParams: ['display', 'scope'], + scope: ['email'], + scopeDelimiter: ',', + display: 'popup', + oauthType: '2.0', + popupOptions: { width: 580, height: 400 } + }, + + google: { + name: 'google', + url: '/auth/google', + authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth', + redirectUri: null, + requiredUrlParams: ['scope'], + optionalUrlParams: ['display'], + scope: ['profile', 'email'], + scopePrefix: 'openid', + scopeDelimiter: ' ', + display: 'popup', + oauthType: '2.0', + popupOptions: { width: 452, height: 633 } + }, + + github: { + name: 'github', + url: '/auth/github', + authorizationEndpoint: 'https://github.com/login/oauth/authorize', + redirectUri: null, + optionalUrlParams: ['scope'], + scope: ['user:email'], + scopeDelimiter: ' ', + oauthType: '2.0', + popupOptions: { width: 1020, height: 618 } + }, + + twitter: { + name: 'twitter', + url: '/auth/twitter', + authorizationEndpoint: 'https://api.twitter.com/oauth/authenticate', + redirectUri: null, + oauthType: '1.0', + popupOptions: { width: 495, height: 645 } + } + } +} \ No newline at end of file diff --git a/src/promise.js b/src/promise.js new file mode 100644 index 0000000..dae3050 --- /dev/null +++ b/src/promise.js @@ -0,0 +1,2 @@ +import Promise from 'promise-polyfill' +export default Promise; \ No newline at end of file diff --git a/src/storage.js b/src/storage.js new file mode 100644 index 0000000..d1be289 --- /dev/null +++ b/src/storage.js @@ -0,0 +1,26 @@ +import LocalStorage from './storage/local-storage.js' +import MemoryStorage from './storage/memory-storage.js' +import SessionStorage from './storage/session-storage.js' + +export default function StorageFactory(options) { + switch (options.storageType) { + case 'localStorage': + try { + window.localStorage.setItem('testKey', 'test') + window.localStorage.removeItem('testKey') + return new LocalStorage(options.storageNamespace) + } catch(e) {} + + case 'sessionStorage': + try { + window.sessionStorage.setItem('testKey', 'test') + window.sessionStorage.removeItem('testKey') + return new SessionStorage(options.storageNamespace) + } catch(e) {} + + case 'memoryStorage': + default: + return new MemoryStorage(options.storageNamespace) + break; + } +} \ No newline at end of file diff --git a/src/storage/local-storage.js b/src/storage/local-storage.js new file mode 100644 index 0000000..efec8d6 --- /dev/null +++ b/src/storage/local-storage.js @@ -0,0 +1,26 @@ +class LocalStorage { + constructor(namespace) { + this.namespace = namespace || null + } + + setItem(key, value) { + window.localStorage.setItem(this._getStorageKey(key), value) + } + + getItem(key) { + return window.localStorage.getItem(this._getStorageKey(key)) + } + + removeItem(key) { + window.localStorage.removeItem(this._getStorageKey(key)) + } + + _getStorageKey(key) { + if (this.namespace) { + return [this.namespace, key].join('.') + } + return key; + } +} + +export default LocalStorage \ No newline at end of file diff --git a/src/storage/memory-storage.js b/src/storage/memory-storage.js new file mode 100644 index 0000000..6b35600 --- /dev/null +++ b/src/storage/memory-storage.js @@ -0,0 +1,27 @@ +class MemoryStorage { + constructor(namespace) { + this.namespace = namespace || null + this._storage = {} + } + + setItem(key, value) { + this._storage[this._getStorageKey(key)] = value + } + + getItem(key) { + return this._storage[this._getStorageKey(key)] + } + + removeItem(key) { + delete this._storage[this._getStorageKey(key)] + } + + _getStorageKey(key) { + if (this.namespace) { + return [this.namespace, key].join('.') + } + return key; + } +} + +export default MemoryStorage \ No newline at end of file diff --git a/src/storage/session-storage.js b/src/storage/session-storage.js new file mode 100644 index 0000000..71725ef --- /dev/null +++ b/src/storage/session-storage.js @@ -0,0 +1,26 @@ +class LocalStorage { + constructor(namespace) { + this.namespace = namespace || null + } + + setItem(key, value) { + window.sessionStorage.setItem(this._getStorageKey(key), value) + } + + getItem(key) { + return window.sessionStorage.getItem(this._getStorageKey(key)) + } + + removeItem(key) { + window.sessionStorage.removeItem(this._getStorageKey(key)) + } + + _getStorageKey(key) { + if (this.namespace) { + return [this.namespace, key].join('.') + } + return key; + } +} + +export default LocalStorage \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..2463a6a --- /dev/null +++ b/src/utils.js @@ -0,0 +1,210 @@ +if (typeof Object.assign != 'function') { + Object.assign = function(target, varArgs) { + 'use strict'; + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +export function camelCase(name) { + return name.replace(/([\:\-\_]+(.))/g, (_, separator, letter, offset) => { + return offset ? letter.toUpperCase() : letter; + }); +} + +export function isUndefined(value) { + return typeof value === 'undefined' +} + +export function isDefined(value) { + return typeof value !== 'undefined' +} + +export function isObject(value) { + return value !== null && typeof value === 'object' +} + +export function isString(value) { + return typeof value === 'string' +} + +export function isNumber(value) { + return typeof value === 'number' +} + +export function isFunction(value) { + return typeof value === 'function' +} + +export function objectExtend(a, b) { + + // Don't touch 'null' or 'undefined' objects. + if (a == null || b == null) { + return a; + } + + Object.keys(b).forEach(function (key) { + if (Object.prototype.toString.call(b[key]) == '[object Object]') { + if (Object.prototype.toString.call(a[key]) != '[object Object]') { + a[key] = b[key]; + } else { + a[key] = objectExtend(a[key], b[key]); + } + } else { + a[key] = b[key]; + } + }); + + return a; +}; + +/** + * Assemble url from two segments + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} baseUrl Base url + * @param {String} url URI + * @return {String} + */ +export function joinUrl(baseUrl, url) { + if (/^(?:[a-z]+:)?\/\//i.test(url)) { + return url; + } + let joined = [baseUrl, url].join('/'); + let normalize = function (str) { + return str + .replace(/[\/]+/g, '/') + .replace(/\/\?/g, '?') + .replace(/\/\#/g, '#') + .replace(/\:\//g, '://'); + }; + return normalize(joined); +} + +/** + * Get full path based on current location + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {Location} location + * @return {String} + */ +export function getFullUrlPath(location) { + const isHttps = location.protocol === 'https:'; + return location.protocol + '//' + location.hostname + + ':' + (location.port || (isHttps ? '443' : '80')) + + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); +} + +/** + * Parse query string variables + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} Query string + * @return {String} + */ +export function parseQueryString(str) { + let obj = {}; + let key; + let value; + (str || '').split('&').forEach((keyValue) => { + if (keyValue) { + value = keyValue.split('='); + key = decodeURIComponent(value[0]); + obj[key] = (!!value[1]) ? decodeURIComponent(value[1]) : true; + } + }); + return obj; +} + +/** + * Decode base64 string + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} str base64 encoded string + * @return {Object} + */ +export function decodeBase64(str) { + let buffer; + if (typeof module !== 'undefined' && module.exports) { + try { + buffer = require('buffer').Buffer; + } catch (err) { + // noop + } + } + + let fromCharCode = String.fromCharCode; + + let re_btou = new RegExp([ + '[\xC0-\xDF][\x80-\xBF]', + '[\xE0-\xEF][\x80-\xBF]{2}', + '[\xF0-\xF7][\x80-\xBF]{3}' + ].join('|'), 'g'); + + let cb_btou = function (cccc) { + switch (cccc.length) { + case 4: + let cp = ((0x07 & cccc.charCodeAt(0)) << 18) + | ((0x3f & cccc.charCodeAt(1)) << 12) + | ((0x3f & cccc.charCodeAt(2)) << 6) + | (0x3f & cccc.charCodeAt(3)); + let offset = cp - 0x10000; + return (fromCharCode((offset >>> 10) + 0xD800) + + fromCharCode((offset & 0x3FF) + 0xDC00)); + case 3: + return fromCharCode( + ((0x0f & cccc.charCodeAt(0)) << 12) + | ((0x3f & cccc.charCodeAt(1)) << 6) + | (0x3f & cccc.charCodeAt(2)) + ); + default: + return fromCharCode( + ((0x1f & cccc.charCodeAt(0)) << 6) + | (0x3f & cccc.charCodeAt(1)) + ); + } + }; + + let btou = function (b) { + return b.replace(re_btou, cb_btou); + }; + + let _decode = buffer ? function (a) { + return (a.constructor === buffer.constructor + ? a : new buffer(a, 'base64')).toString(); + } + : function (a) { + return btou(atob(a)); + }; + + return _decode( + String(str).replace(/[-_]/g, function (m0) { + return m0 === '-' ? '+' : '/'; + }) + .replace(/[^A-Za-z0-9\+\/]/g, '') + ); +} \ No newline at end of file diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..2b94ab6 --- /dev/null +++ b/test/config.json @@ -0,0 +1,20 @@ +{ + "auth": { + "google": { + "clientId": "", + "clientSecret": "" + }, + "facebook": { + "clientId": "", + "clientSecret": "" + }, + "github": { + "clientId": "", + "clientSecret": "" + }, + "twitter": { + "clientId": "", + "clientSecret": "" + } + } +} \ No newline at end of file diff --git a/test/index.bundle.js b/test/index.bundle.js new file mode 100644 index 0000000..fc5bba1 --- /dev/null +++ b/test/index.bundle.js @@ -0,0 +1,17224 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; +/******/ +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 17); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(module) {/* harmony export (immutable) */ __webpack_exports__["g"] = camelCase; +/* unused harmony export isUndefined */ +/* unused harmony export isDefined */ +/* harmony export (immutable) */ __webpack_exports__["c"] = isObject; +/* harmony export (immutable) */ __webpack_exports__["d"] = isString; +/* unused harmony export isNumber */ +/* harmony export (immutable) */ __webpack_exports__["b"] = isFunction; +/* harmony export (immutable) */ __webpack_exports__["a"] = objectExtend; +/* harmony export (immutable) */ __webpack_exports__["f"] = joinUrl; +/* harmony export (immutable) */ __webpack_exports__["h"] = getFullUrlPath; +/* harmony export (immutable) */ __webpack_exports__["i"] = parseQueryString; +/* harmony export (immutable) */ __webpack_exports__["e"] = decodeBase64; +if (typeof Object.assign != 'function') { + Object.assign = function (target, varArgs) { + 'use strict'; + + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { + // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +function camelCase(name) { + return name.replace(/([\:\-\_]+(.))/g, (_, separator, letter, offset) => { + return offset ? letter.toUpperCase() : letter; + }); +} + +function isUndefined(value) { + return typeof value === 'undefined'; +} + +function isDefined(value) { + return typeof value !== 'undefined'; +} + +function isObject(value) { + return value !== null && typeof value === 'object'; +} + +function isString(value) { + return typeof value === 'string'; +} + +function isNumber(value) { + return typeof value === 'number'; +} + +function isFunction(value) { + return typeof value === 'function'; +} + +function objectExtend(a, b) { + + // Don't touch 'null' or 'undefined' objects. + if (a == null || b == null) { + return a; + } + + Object.keys(b).forEach(function (key) { + if (Object.prototype.toString.call(b[key]) == '[object Object]') { + if (Object.prototype.toString.call(a[key]) != '[object Object]') { + a[key] = b[key]; + } else { + a[key] = objectExtend(a[key], b[key]); + } + } else { + a[key] = b[key]; + } + }); + + return a; +}; + +/** + * Assemble url from two segments + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} baseUrl Base url + * @param {String} url URI + * @return {String} + */ +function joinUrl(baseUrl, url) { + if (/^(?:[a-z]+:)?\/\//i.test(url)) { + return url; + } + let joined = [baseUrl, url].join('/'); + let normalize = function (str) { + return str.replace(/[\/]+/g, '/').replace(/\/\?/g, '?').replace(/\/\#/g, '#').replace(/\:\//g, '://'); + }; + return normalize(joined); +} + +/** + * Get full path based on current location + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {Location} location + * @return {String} + */ +function getFullUrlPath(location) { + const isHttps = location.protocol === 'https:'; + return location.protocol + '//' + location.hostname + ':' + (location.port || (isHttps ? '443' : '80')) + (/^\//.test(location.pathname) ? location.pathname : '/' + location.pathname); +} + +/** + * Parse query string variables + * + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} Query string + * @return {String} + */ +function parseQueryString(str) { + let obj = {}; + let key; + let value; + (str || '').split('&').forEach(keyValue => { + if (keyValue) { + value = keyValue.split('='); + key = decodeURIComponent(value[0]); + obj[key] = !!value[1] ? decodeURIComponent(value[1]) : true; + } + }); + return obj; +} + +/** + * Decode base64 string + * @author Sahat Yalkabov + * @copyright Method taken from https://github.com/sahat/satellizer + * + * @param {String} str base64 encoded string + * @return {Object} + */ +function decodeBase64(str) { + let buffer; + if (typeof module !== 'undefined' && module.exports) { + try { + buffer = __webpack_require__(19).Buffer; + } catch (err) { + // noop + } + } + + let fromCharCode = String.fromCharCode; + + let re_btou = new RegExp(['[\xC0-\xDF][\x80-\xBF]', '[\xE0-\xEF][\x80-\xBF]{2}', '[\xF0-\xF7][\x80-\xBF]{3}'].join('|'), 'g'); + + let cb_btou = function (cccc) { + switch (cccc.length) { + case 4: + let cp = (0x07 & cccc.charCodeAt(0)) << 18 | (0x3f & cccc.charCodeAt(1)) << 12 | (0x3f & cccc.charCodeAt(2)) << 6 | 0x3f & cccc.charCodeAt(3); + let offset = cp - 0x10000; + return fromCharCode((offset >>> 10) + 0xD800) + fromCharCode((offset & 0x3FF) + 0xDC00); + case 3: + return fromCharCode((0x0f & cccc.charCodeAt(0)) << 12 | (0x3f & cccc.charCodeAt(1)) << 6 | 0x3f & cccc.charCodeAt(2)); + default: + return fromCharCode((0x1f & cccc.charCodeAt(0)) << 6 | 0x3f & cccc.charCodeAt(1)); + } + }; + + let btou = function (b) { + return b.replace(re_btou, cb_btou); + }; + + let _decode = buffer ? function (a) { + return (a.constructor === buffer.constructor ? a : new buffer(a, 'base64')).toString(); + } : function (a) { + return btou(atob(a)); + }; + + return _decode(String(str).replace(/[-_]/g, function (m0) { + return m0 === '-' ? '+' : '/'; + }).replace(/[^A-Za-z0-9\+\/]/g, '')); +} +/* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(25)(module))) + +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_promise_polyfill__ = __webpack_require__(22); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_promise_polyfill___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_promise_polyfill__); + +/* harmony default export */ __webpack_exports__["a"] = (__WEBPACK_IMPORTED_MODULE_0_promise_polyfill___default.a); + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || Function("return this")() || (1,eval)("this"); +} catch(e) { + // This works if the window reference is available + if(typeof window === "object") + g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(process, global) {/*! + * Vue.js v2.2.5 + * (c) 2014-2017 Evan You + * Released under the MIT License. + */ + + +/* */ + +/** + * Convert a value to a string that is actually rendered. + */ +function _toString (val) { + return val == null + ? '' + : typeof val === 'object' + ? JSON.stringify(val, null, 2) + : String(val) +} + +/** + * Convert a input value to a number for persistence. + * If the conversion fails, return original string. + */ +function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n +} + +/** + * Make a map and return a function for checking if a key + * is in that map. + */ +function makeMap ( + str, + expectsLowerCase +) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } +} + +/** + * Check if a tag is a built-in tag. + */ +var isBuiltInTag = makeMap('slot,component', true); + +/** + * Remove an item from an array + */ +function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } +} + +/** + * Check whether the object has the property. + */ +var hasOwnProperty = Object.prototype.hasOwnProperty; +function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) +} + +/** + * Check if value is primitive + */ +function isPrimitive (value) { + return typeof value === 'string' || typeof value === 'number' +} + +/** + * Create a cached version of a pure function. + */ +function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) +} + +/** + * Camelize a hyphen-delimited string. + */ +var camelizeRE = /-(\w)/g; +var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}); + +/** + * Capitalize a string. + */ +var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) +}); + +/** + * Hyphenate a camelCase string. + */ +var hyphenateRE = /([^-])([A-Z])/g; +var hyphenate = cached(function (str) { + return str + .replace(hyphenateRE, '$1-$2') + .replace(hyphenateRE, '$1-$2') + .toLowerCase() +}); + +/** + * Simple bind, faster than native + */ +function bind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + // record original fn length + boundFn._length = fn.length; + return boundFn +} + +/** + * Convert an Array-like object to a real Array. + */ +function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret +} + +/** + * Mix properties into target object. + */ +function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to +} + +/** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ +function isObject (obj) { + return obj !== null && typeof obj === 'object' +} + +/** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ +var toString = Object.prototype.toString; +var OBJECT_STRING = '[object Object]'; +function isPlainObject (obj) { + return toString.call(obj) === OBJECT_STRING +} + +/** + * Merge an Array of Objects into a single Object. + */ +function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res +} + +/** + * Perform no operation. + */ +function noop () {} + +/** + * Always return false. + */ +var no = function () { return false; }; + +/** + * Return same value + */ +var identity = function (_) { return _; }; + +/** + * Generate a static keys string from compiler modules. + */ +function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') +} + +/** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ +function looseEqual (a, b) { + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + return JSON.stringify(a) === JSON.stringify(b) + } catch (e) { + // possible circular reference + return a === b + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } +} + +function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 +} + +/** + * Ensure a function is called only once. + */ +function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn(); + } + } +} + +/* */ + +var config = { + /** + * Option merge strategies (used in core/util/options) + */ + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: process.env.NODE_ENV !== 'production', + + /** + * Whether to enable devtools + */ + devtools: process.env.NODE_ENV !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * List of asset types that a component can own. + */ + _assetTypes: [ + 'component', + 'directive', + 'filter' + ], + + /** + * List of lifecycle hooks. + */ + _lifecycleHooks: [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated' + ], + + /** + * Max circular updates allowed in a scheduler flush cycle. + */ + _maxUpdateCount: 100 +}; + +/* */ + +var emptyObject = Object.freeze({}); + +/** + * Check if a string starts with $ or _ + */ +function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F +} + +/** + * Define a property. + */ +function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); +} + +/** + * Parse simple path. + */ +var bailRE = /[^\w.$]/; +function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } +} + +/* */ +/* globals MutationObserver */ + +// can we use __proto__? +var hasProto = '__proto__' in {}; + +// Browser environment sniffing +var inBrowser = typeof window !== 'undefined'; +var UA = inBrowser && window.navigator.userAgent.toLowerCase(); +var isIE = UA && /msie|trident/.test(UA); +var isIE9 = UA && UA.indexOf('msie 9.0') > 0; +var isEdge = UA && UA.indexOf('edge/') > 0; +var isAndroid = UA && UA.indexOf('android') > 0; +var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA); +var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + +// this needs to be lazy-evaled because vue may be required before +// vue-server-renderer can set VUE_ENV +var _isServer; +var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer +}; + +// detect devtools +var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + +/* istanbul ignore next */ +function isNative (Ctor) { + return /native code/.test(Ctor.toString()) +} + +var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + +/** + * Defer a task to execute it asynchronously. + */ +var nextTick = (function () { + var callbacks = []; + var pending = false; + var timerFunc; + + function nextTickHandler () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // the nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore if */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + var logError = function (err) { console.error(err); }; + timerFunc = function () { + p.then(nextTickHandler).catch(logError); + // in problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + } else if (typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // use MutationObserver where native Promise is not available, + // e.g. PhantomJS IE11, iOS7, Android 4.4 + var counter = 1; + var observer = new MutationObserver(nextTickHandler); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + } else { + // fallback to setTimeout + /* istanbul ignore next */ + timerFunc = function () { + setTimeout(nextTickHandler, 0); + }; + } + + return function queueNextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { cb.call(ctx); } + if (_resolve) { _resolve(ctx); } + }); + if (!pending) { + pending = true; + timerFunc(); + } + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } +})(); + +var _Set; +/* istanbul ignore if */ +if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; +} else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = (function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); +} + +var warn = noop; +var tip = noop; +var formatComponentName; + +if (process.env.NODE_ENV !== 'production') { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.error("[Vue warn]: " + msg + " " + ( + vm ? formatLocation(formatComponentName(vm)) : '' + )); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + " " + ( + vm ? formatLocation(formatComponentName(vm)) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var name = typeof vm === 'string' + ? vm + : typeof vm === 'function' && vm.options + ? vm.options.name + : vm._isVue + ? vm.$options.name || vm.$options._componentTag + : vm.name; + + var file = vm._isVue && vm.$options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var formatLocation = function (str) { + if (str === "") { + str += " - use the \"name\" option for better debugging messages."; + } + return ("\n(found in " + str + ")") + }; +} + +/* */ + + +var uid$1 = 0; + +/** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ +var Dep = function Dep () { + this.id = uid$1++; + this.subs = []; +}; + +Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); +}; + +Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); +}; + +Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } +}; + +Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } +}; + +// the current target watcher being evaluated. +// this is globally unique because there could be only one +// watcher being evaluated at any time. +Dep.target = null; +var targetStack = []; + +function pushTarget (_target) { + if (Dep.target) { targetStack.push(Dep.target); } + Dep.target = _target; +} + +function popTarget () { + Dep.target = targetStack.pop(); +} + +/* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + +var arrayProto = Array.prototype; +var arrayMethods = Object.create(arrayProto);[ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] +.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var arguments$1 = arguments; + + // avoid leaking arguments: + // http://jsperf.com/closure-with-arguments + var i = arguments.length; + var args = new Array(i); + while (i--) { + args[i] = arguments$1[i]; + } + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + inserted = args; + break + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); +}); + +/* */ + +var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + +/** + * By default, when a reactive property is set, the new value is + * also converted to become reactive. However when passing down props, + * we don't want to force conversion because the value may be a nested value + * under a frozen data structure. Converting it would defeat the optimization. + */ +var observerState = { + shouldConvert: true, + isSettingProps: false +}; + +/** + * Observer class that are attached to each observed + * object. Once attached, the observer converts target + * object's property keys into getter/setters that + * collect dependencies and dispatches updates. + */ +var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + var augment = hasProto + ? protoAugment + : copyAugment; + augment(value, arrayMethods, arrayKeys); + this.observeArray(value); + } else { + this.walk(value); + } +}; + +/** + * Walk through each property and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ +Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i], obj[keys[i]]); + } +}; + +/** + * Observe a list of Array items. + */ +Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } +}; + +// helpers + +/** + * Augment an target Object or Array by intercepting + * the prototype chain using __proto__ + */ +function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ +} + +/** + * Augment an target Object or Array by defining + * hidden properties. + */ +/* istanbul ignore next */ +function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } +} + +/** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ +function observe (value, asRootData) { + if (!isObject(value)) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + observerState.shouldConvert && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob +} + +/** + * Define a reactive property on an Object. + */ +function defineReactive$$1 ( + obj, + key, + val, + customSetter +) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + + var childOb = observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + } + if (Array.isArray(value)) { + dependArray(value); + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (process.env.NODE_ENV !== 'production' && customSetter) { + customSetter(); + } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = observe(newVal); + dep.notify(); + } + }); +} + +/** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ +function set (target, key, val) { + if (Array.isArray(target) && typeof key === 'number') { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (hasOwn(target, key)) { + target[key] = val; + return val + } + var ob = (target ).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val +} + +/** + * Delete a property and trigger change if necessary. + */ +function del (target, key) { + if (Array.isArray(target) && typeof key === 'number') { + target.splice(key, 1); + return + } + var ob = (target ).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); +} + +/** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ +function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } +} + +/* */ + +/** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ +var strats = config.optionMergeStrategies; + +/** + * Options with restrictions + */ +if (process.env.NODE_ENV !== 'production') { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; +} + +/** + * Helper that recursively merges two data objects together. + */ +function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + var keys = Object.keys(from); + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { + mergeData(toVal, fromVal); + } + } + return to +} + +/** + * Data + */ +strats.data = function ( + parentVal, + childVal, + vm +) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (typeof childVal !== 'function') { + process.env.NODE_ENV !== 'production' && warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + childVal.call(this), + parentVal.call(this) + ) + } + } else if (parentVal || childVal) { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm) + : undefined; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } +}; + +/** + * Hooks and props are merged as arrays. + */ +function mergeHook ( + parentVal, + childVal +) { + return childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal +} + +config._lifecycleHooks.forEach(function (hook) { + strats[hook] = mergeHook; +}); + +/** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ +function mergeAssets (parentVal, childVal) { + var res = Object.create(parentVal || null); + return childVal + ? extend(res, childVal) + : res +} + +config._assetTypes.forEach(function (type) { + strats[type + 's'] = mergeAssets; +}); + +/** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ +strats.watch = function (parentVal, childVal) { + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key in childVal) { + var parent = ret[key]; + var child = childVal[key]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key] = parent + ? parent.concat(child) + : [child]; + } + return ret +}; + +/** + * Other object hashes. + */ +strats.props = +strats.methods = +strats.computed = function (parentVal, childVal) { + if (!childVal) { return Object.create(parentVal || null) } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + extend(ret, childVal); + return ret +}; + +/** + * Default strategy. + */ +var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal +}; + +/** + * Validate component names + */ +function checkComponents (options) { + for (var key in options.components) { + var lower = key.toLowerCase(); + if (isBuiltInTag(lower) || config.isReservedTag(lower)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + key + ); + } + } +} + +/** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ +function normalizeProps (options) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else if (process.env.NODE_ENV !== 'production') { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } + options.props = res; +} + +/** + * Normalize raw function directives into object format. + */ +function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def = dirs[key]; + if (typeof def === 'function') { + dirs[key] = { bind: def, update: def }; + } + } + } +} + +/** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ +function mergeOptions ( + parent, + child, + vm +) { + if (process.env.NODE_ENV !== 'production') { + checkComponents(child); + } + normalizeProps(child); + normalizeDirectives(child); + var extendsFrom = child.extends; + if (extendsFrom) { + parent = typeof extendsFrom === 'function' + ? mergeOptions(parent, extendsFrom.options, vm) + : mergeOptions(parent, extendsFrom, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + var mixin = child.mixins[i]; + if (mixin.prototype instanceof Vue$3) { + mixin = mixin.options; + } + parent = mergeOptions(parent, mixin, vm); + } + } + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options +} + +/** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ +function resolveAsset ( + options, + type, + id, + warnMissing +) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (process.env.NODE_ENV !== 'production' && warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res +} + +/* */ + +function validateProp ( + key, + propOptions, + propsData, + vm +) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // handle boolean props + if (isType(Boolean, prop.type)) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) { + value = true; + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldConvert = observerState.shouldConvert; + observerState.shouldConvert = true; + observe(value); + observerState.shouldConvert = prevShouldConvert; + } + if (process.env.NODE_ENV !== 'production') { + assertProp(prop, key, value, vm, absent); + } + return value +} + +/** + * Get the default value of a prop. + */ +function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (process.env.NODE_ENV !== 'production' && isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def +} + +/** + * Assert whether a prop is valid. + */ +function assertProp ( + prop, + name, + value, + vm, + absent +) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + if (!valid) { + warn( + 'Invalid prop: type check failed for prop "' + name + '".' + + ' Expected ' + expectedTypes.map(capitalize).join(', ') + + ', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.', + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } +} + +/** + * Assert the type of a value + */ +function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (expectedType === 'String') { + valid = typeof value === (expectedType = 'string'); + } else if (expectedType === 'Number') { + valid = typeof value === (expectedType = 'number'); + } else if (expectedType === 'Boolean') { + valid = typeof value === (expectedType = 'boolean'); + } else if (expectedType === 'Function') { + valid = typeof value === (expectedType = 'function'); + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } +} + +/** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ +function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match && match[1] +} + +function isType (type, fn) { + if (!Array.isArray(fn)) { + return getType(fn) === getType(type) + } + for (var i = 0, len = fn.length; i < len; i++) { + if (getType(fn[i]) === getType(type)) { + return true + } + } + /* istanbul ignore next */ + return false +} + +function handleError (err, vm, info) { + if (config.errorHandler) { + config.errorHandler.call(null, err, vm, info); + } else { + if (process.env.NODE_ENV !== 'production') { + warn(("Error in " + info + ":"), vm); + } + /* istanbul ignore else */ + if (inBrowser && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } +} + +/* not type checking this file because flow doesn't play well with Proxy */ + +var initProxy; + +if (process.env.NODE_ENV !== 'production') { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + "referenced during render. Make sure to declare reactive data " + + "properties in the data option.", + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && + Proxy.toString().match(/native code/); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || key.charAt(0) === '_'; + if (!has && !isAllowed) { + warnNonPresent(target, key); + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + warnNonPresent(target, key); + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; +} + +var mark; +var measure; + +if (process.env.NODE_ENV !== 'production') { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + perf.clearMeasures(name); + }; + } +} + +/* */ + +var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions +) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.functionalContext = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; +}; + +var prototypeAccessors = { child: {} }; + +// DEPRECATED: alias for componentInstance for backwards compat. +/* istanbul ignore next */ +prototypeAccessors.child.get = function () { + return this.componentInstance +}; + +Object.defineProperties( VNode.prototype, prototypeAccessors ); + +var createEmptyVNode = function () { + var node = new VNode(); + node.text = ''; + node.isComment = true; + return node +}; + +function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) +} + +// optimized shallow clone +// used for static nodes and slot nodes because they may be reused across +// multiple renders, cloning them avoids errors when DOM manipulations rely +// on their elm reference. +function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + vnode.children, + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isCloned = true; + return cloned +} + +function cloneVNodes (vnodes) { + var len = vnodes.length; + var res = new Array(len); + for (var i = 0; i < len; i++) { + res[i] = cloneVNode(vnodes[i]); + } + return res +} + +/* */ + +var normalizeEvent = cached(function (name) { + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture + } +}); + +function createFnInvoker (fns) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + for (var i = 0; i < fns.length; i++) { + fns[i].apply(null, arguments$1); + } + } else { + // return handler return value for single handlers + return fns.apply(null, arguments) + } + } + invoker.fns = fns; + return invoker +} + +function updateListeners ( + on, + oldOn, + add, + remove$$1, + vm +) { + var name, cur, old, event; + for (name in on) { + cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (!cur) { + process.env.NODE_ENV !== 'production' && warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (!old) { + if (!cur.fns) { + cur = on[name] = createFnInvoker(cur); + } + add(event.name, cur, event.once, event.capture); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (!on[name]) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } +} + +/* */ + +function mergeVNodeHook (def, hookKey, hook) { + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (!oldHook) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (oldHook.fns && oldHook.merged) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; +} + +/* */ + +// The template compiler attempts to minimize the need for normalization by +// statically analyzing the template at compile time. +// +// For plain HTML markup, normalization can be completely skipped because the +// generated render function is guaranteed to return Array. There are +// two cases where extra normalization is needed: + +// 1. When the children contains components - because a functional component +// may return an Array instead of a single root. In this case, just a simple +// normalization is needed - if any child is an Array, we flatten the whole +// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep +// because functional components already normalize their own children. +function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children +} + +// 2. When the children contains constructs that always generated nested Arrays, +// e.g.