├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .stylelintrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bs-config.json ├── build ├── build.js ├── utils │ ├── index.js │ ├── log.js │ ├── style.js │ └── write.js ├── webpack.config.base.js ├── webpack.config.dev.js └── webpack.config.dll.js ├── dist ├── v-wechat-auth.common.js ├── v-wechat-auth.esm.js ├── v-wechat-auth.js └── v-wechat-auth.min.js ├── examples ├── config.example.js └── index.html ├── images └── openid.png ├── package-lock.json ├── package.json ├── src ├── config.js ├── index.js └── install.js └── test ├── .eslintrc ├── helpers ├── Test.vue ├── index.js ├── utils.js └── wait-for-update.js ├── index.js ├── karma.conf.js └── visual.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "last 2 versions" 9 | ] 10 | } 11 | } 12 | ] 13 | ], 14 | "plugins": [ 15 | "transform-vue-jsx", 16 | "transform-object-rest-spread", 17 | "transform-runtime" 18 | ], 19 | "env": { 20 | "test": { 21 | "plugins": [ 22 | "istanbul" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/*.js 2 | 3 | examples/** 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | extends: 'vue', 8 | // add your custom rules here 9 | 'rules': { 10 | // allow async-await 11 | 'generator-star-spacing': 0, 12 | // allow debugger during development 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 14 | }, 15 | globals: { 16 | requestAnimationFrame: true, 17 | performance: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | test/coverage 5 | yarn-error.log 6 | reports 7 | test/dist 8 | examples/config.js 9 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-html"], 3 | "extends": "stylelint-config-standard", 4 | "rules": { 5 | "no-empty-source": null 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raychenfj/v-wechat-auth/42e6e1b6f5070b6adecbec7b59b4d22bc423a836/CONTRIBUTING.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 fengjun.chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v-wechat-auth 2 | 3 | [![npm](https://img.shields.io/npm/v/v-wechat-auth.svg)](https://www.npmjs.com/package/v-wechat-auth) [![vue2](https://img.shields.io/badge/vue-2.x-brightgreen.svg)](https://vuejs.org/) 4 | 5 | > vue 2.0 微信网页授权插件 6 | 7 | ## 安装 8 | 9 | ### npm 10 | 11 | ```bash 12 | npm install --save v-wechat-auth 13 | ``` 14 | 15 | ```js 16 | import Vue from 'vue' 17 | import VWechatAuth from 'v-wechat-auth' 18 | ``` 19 | 20 | ### 通过 script 标签 21 | 22 | ```html 23 | 24 | 25 | 26 | 27 | 28 | 29 | ``` 30 | 31 | ## 使用 32 | 33 | ### 初始化 34 | 35 | ```js 36 | Vue.use(VWechatAuth) 37 | 38 | // 必须在 root 实例上注入 wechatAuth 39 | new Vue({ 40 | el: '#app', 41 | ..., 42 | wechatAuth: new VWechatAuth({ 43 | appId: 'your wechat appid', 44 | scope: 'snsapi_base or snsapi_userinfo' 45 | authorize () { 46 | return axios.get('your backend api here', { params: { code: code } }) 47 | .then(function (res) { 48 | var data = (res && res.data) || {} // response data should at least contain openid 49 | return data 50 | }) 51 | } 52 | }) 53 | }) 54 | ``` 55 | 56 | ### 调用 57 | 58 | ```js 59 | this.$wechatAuth.authorize() 60 | ``` 61 | 62 | ## 运行例子 63 | 64 | 1. 将 ```examples``` 文件夹中的 ```config.example.js``` 重命名为 ```config.js``` 65 | 66 | 2. 修改 ```config.js```, 填入微信 appid 和 scope, 并在 ```authorize``` 方法中调用后端接口获取用户信息,并将用户信息返回 67 | 68 | 4. 修改 ```host``` 文件,将微信授权域名映射为 ```localhost``` 69 | 70 | 3. 运行 ```npm run example``` 71 | 72 | 4. 在微信开发者工具中访问 ```授权域名/examples/index.html```(因为修改了host,这时访问的实际上是本地) 73 | 74 | 5. 能显示当前用户的 ```openid``` 75 | 76 | ![openid](./images/openid.png) 77 | 78 | 79 | ## options 80 | 81 | | 属性 | 类型 | 必输 | 默认 | 说明 | 82 | |-------|-------|-------|-------|-------| 83 | | autoRedirect | boolean | 否 | true | 当 url 中不包含 code 参数或返回结果中不包含openid时,是否自动重定向到微信授权url | 84 | | appId | string | 是 | | 微信 appid | 85 | | scope | string | 是 | | 微信 scope, ```snsapi_base``` 或 ```snsapi_userinfo``` | 86 | | state | string | 否 | '' | 微信 state | 87 | | authorize | function | 是 | | ajax 请求调用后端接口获取微信用户信息 88 | | ssr | boolean | 否 | | 是否使用服务端渲染,尚未实现 | 89 | 90 | ## VWechatAuth 实例 91 | 92 | | 属性 | 说明 | 93 | |------|------| 94 | | user | 当获取用户信息成功后,存储了用户的信息,否则为一个空对象 | 95 | 96 | | 方法 | 参数 | 返回 | 说明 | 97 | |------|------|-------|------| 98 | | authorize | onSuccess,执行成功的回调函数
onFail,执行失败的回调函数 | Promise | 授权获取用户信息,支持回调函数 和 Promise 99 | 100 | 101 | 102 | ## License 103 | 104 | [MIT](http://opensource.org/licenses/MIT) 105 | -------------------------------------------------------------------------------- /bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 80, 3 | "startPath": "example/index.html" 4 | } -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | const mkdirp = require('mkdirp') 2 | const rollup = require('rollup').rollup 3 | const vue = require('rollup-plugin-vue') 4 | const jsx = require('rollup-plugin-jsx') 5 | const buble = require('rollup-plugin-buble') 6 | const replace = require('rollup-plugin-replace') 7 | const cjs = require('rollup-plugin-commonjs') 8 | const node = require('rollup-plugin-node-resolve') 9 | const uglify = require('uglify-js') 10 | const CleanCSS = require('clean-css') 11 | 12 | // Make sure dist dir exists 13 | mkdirp('dist') 14 | 15 | const { 16 | logError, 17 | write, 18 | banner, 19 | name, 20 | moduleName, 21 | version, 22 | processStyle 23 | } = require('./utils') 24 | 25 | function rollupBundle ({ env }) { 26 | return rollup({ 27 | entry: 'src/index.js', 28 | plugins: [ 29 | node({ 30 | extensions: ['.js', '.jsx', '.vue'], 31 | preferBuiltins: false 32 | }), 33 | cjs(), 34 | vue({ 35 | compileTemplate: true, 36 | css (styles, stylesNodes) { 37 | // Only generate the styles once 38 | if (env['process.env.NODE_ENV'] === '"production"') { 39 | Promise.all( 40 | stylesNodes.map(processStyle) 41 | ).then(css => { 42 | const result = css.map(c => c.css).join('') 43 | // write the css for every component 44 | // TODO add it back if we extract all components to individual js 45 | // files too 46 | // css.forEach(writeCss) 47 | if (result) { 48 | write(`dist/${name}.css`, result) 49 | write(`dist/${name}.min.css`, new CleanCSS().minify(result).styles) 50 | } 51 | }).catch(logError) 52 | } 53 | } 54 | }), 55 | jsx({ factory: 'h' }), 56 | replace(Object.assign({ 57 | __VERSION__: version 58 | }, env)), 59 | buble({ 60 | objectAssign: 'Object.assign' 61 | }) 62 | ] 63 | }) 64 | } 65 | 66 | const bundleOptions = { 67 | banner, 68 | exports: 'default', 69 | format: 'umd', 70 | moduleName 71 | } 72 | 73 | function createBundle ({ name, env, format }) { 74 | return rollupBundle({ 75 | env 76 | }).then(function (bundle) { 77 | const options = Object.assign({}, bundleOptions) 78 | if (format) options.format = format 79 | const code = bundle.generate(options).code 80 | if (/min$/.test(name)) { 81 | const minified = uglify.minify(code, { 82 | output: { 83 | preamble: banner, 84 | ascii_only: true // eslint-disable-line camelcase 85 | } 86 | }).code 87 | return write(`dist/${name}.js`, minified) 88 | } else { 89 | return write(`dist/${name}.js`, code) 90 | } 91 | }).catch(logError) 92 | } 93 | 94 | // Browser bundle (can be used with script) 95 | createBundle({ 96 | name: `${name}`, 97 | env: { 98 | 'process.env.NODE_ENV': '"development"' 99 | } 100 | }) 101 | 102 | // Commonjs bundle (preserves process.env.NODE_ENV) so 103 | // the user can replace it in dev and prod mode 104 | createBundle({ 105 | name: `${name}.common`, 106 | env: {}, 107 | format: 'cjs' 108 | }) 109 | 110 | // uses export and import syntax. Should be used with modern bundlers 111 | // like rollup and webpack 2 112 | createBundle({ 113 | name: `${name}.esm`, 114 | env: {}, 115 | format: 'es' 116 | }) 117 | 118 | // Minified version for browser 119 | createBundle({ 120 | name: `${name}.min`, 121 | env: { 122 | 'process.env.NODE_ENV': '"production"' 123 | } 124 | }) 125 | -------------------------------------------------------------------------------- /build/utils/index.js: -------------------------------------------------------------------------------- 1 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 2 | const { join } = require('path') 3 | 4 | const { 5 | red, 6 | logError 7 | } = require('./log') 8 | 9 | const { 10 | processStyle 11 | } = require('./style') 12 | 13 | const uppercamelcase = require('uppercamelcase') 14 | 15 | exports.write = require('./write') 16 | 17 | const { 18 | author, 19 | name, 20 | version, 21 | dllPlugin 22 | } = require('../../package.json') 23 | 24 | const authorName = author.replace(/\s+<.*/, '') 25 | const minExt = process.env.NODE_ENV === 'production' ? '.min' : '' 26 | 27 | exports.author = authorName 28 | exports.version = version 29 | exports.dllName = dllPlugin.name 30 | exports.moduleName = uppercamelcase(name) 31 | exports.name = name 32 | exports.filename = name + minExt 33 | exports.banner = `/*! 34 | * ${name} v${version} 35 | * (c) ${new Date().getFullYear()} ${authorName} 36 | * Released under the MIT License. 37 | */ 38 | ` 39 | 40 | // log.js 41 | exports.red = red 42 | exports.logError = logError 43 | 44 | // It'd be better to add a sass property to the vue-loader options 45 | // but it simply don't work 46 | const sassOptions = { 47 | includePaths: [ 48 | join(__dirname, '../../node_modules') 49 | ] 50 | } 51 | 52 | // don't extract css in test mode 53 | const nullLoader = process.env.NODE_ENV === 'common' ? 'null-loader!' : '' 54 | exports.vueLoaders = 55 | process.env.BABEL_ENV === 'test' ? { 56 | css: 'css-loader', 57 | scss: `css-loader!sass-loader?${JSON.stringify(sassOptions)}` 58 | } : { 59 | css: ExtractTextPlugin.extract(`${nullLoader}css-loader`), 60 | scss: ExtractTextPlugin.extract( 61 | `${nullLoader}css-loader!sass-loader?${JSON.stringify(sassOptions)}` 62 | ) 63 | } 64 | 65 | // style.js 66 | exports.processStyle = processStyle 67 | -------------------------------------------------------------------------------- /build/utils/log.js: -------------------------------------------------------------------------------- 1 | function logError (e) { 2 | console.log(e) 3 | } 4 | 5 | function blue (str) { 6 | return `\x1b[1m\x1b[34m${str}\x1b[39m\x1b[22m` 7 | } 8 | 9 | function green (str) { 10 | return `\x1b[1m\x1b[32m${str}\x1b[39m\x1b[22m` 11 | } 12 | 13 | function red (str) { 14 | return `\x1b[1m\x1b[31m${str}\x1b[39m\x1b[22m` 15 | } 16 | 17 | function yellow (str) { 18 | return `\x1b[1m\x1b[33m${str}\x1b[39m\x1b[22m` 19 | } 20 | 21 | module.exports = { 22 | blue, 23 | green, 24 | red, 25 | yellow, 26 | logError 27 | } 28 | -------------------------------------------------------------------------------- /build/utils/style.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const postcss = require('postcss') 3 | const cssnext = require('postcss-cssnext') 4 | const CleanCSS = require('clean-css') 5 | const { logError } = require('./log.js') 6 | const write = require('./write.js') 7 | 8 | function processCss (style) { 9 | const componentName = path.basename(style.id, '.vue') 10 | return postcss([cssnext()]) 11 | .process(style.code, {}) 12 | .then(result => { 13 | return { 14 | name: componentName, 15 | css: result.css, 16 | map: result.map 17 | } 18 | }) 19 | } 20 | 21 | let stylus 22 | function processStylus (style) { 23 | try { 24 | stylus = stylus || require('stylus') 25 | } catch (e) { 26 | logError(e) 27 | } 28 | const componentName = path.basename(style.id, '.vue') 29 | return new Promise((resolve, reject) => { 30 | stylus.render(style.code, function (err, css) { 31 | if (err) return reject(err) 32 | resolve({ 33 | original: { 34 | code: style.code, 35 | ext: 'styl' 36 | }, 37 | name: componentName, 38 | css 39 | }) 40 | }) 41 | }) 42 | } 43 | 44 | function processStyle (style) { 45 | if (style.lang === 'css') { 46 | return processCss(style) 47 | } else if (style.lang === 'stylus') { 48 | return processStylus(style) 49 | } else { 50 | throw new Error(`Unknown style language '${style.lang}'`) 51 | } 52 | } 53 | 54 | function writeCss (style) { 55 | write(`dist/${style.name}.css`, style.css) 56 | if (style.original) { 57 | write(`dist/${style.name}.${style.original.ext}`, style.original.code) 58 | } 59 | if (style.map) write(`dist/${style.name}.css.map`, style.map) 60 | write(`dist/${style.name}.min.css`, new CleanCSS().minify(style.css).styles) 61 | } 62 | 63 | module.exports = { 64 | writeCss, 65 | processStyle 66 | } 67 | -------------------------------------------------------------------------------- /build/utils/write.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const { blue } = require('./log.js') 4 | 5 | function write (dest, code) { 6 | return new Promise(function (resolve, reject) { 7 | fs.writeFile(dest, code, function (err) { 8 | if (err) return reject(err) 9 | console.log(blue(dest) + ' ' + getSize(code)) 10 | resolve(code) 11 | }) 12 | }) 13 | } 14 | 15 | function getSize (code) { 16 | return (code.length / 1024).toFixed(2) + 'kb' 17 | } 18 | 19 | module.exports = write 20 | -------------------------------------------------------------------------------- /build/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | const { resolve } = require('path') 4 | 5 | const { 6 | banner, 7 | filename, 8 | version, 9 | vueLoaders 10 | } = require('./utils') 11 | 12 | const plugins = [ 13 | new webpack.DefinePlugin({ 14 | '__VERSION__': JSON.stringify(version), 15 | 'process.env.NODE_ENV': '"test"' 16 | }), 17 | new webpack.BannerPlugin({ banner, raw: true, entryOnly: true }), 18 | new ExtractTextPlugin({ 19 | filename: `${filename}.css`, 20 | // Don't extract css in test mode 21 | disable: /^(common|test)$/.test(process.env.NODE_ENV) 22 | }) 23 | ] 24 | 25 | module.exports = { 26 | output: { 27 | path: resolve(__dirname, '../dist'), 28 | filename: `${filename}.common.js` 29 | }, 30 | entry: './src/index.js', 31 | resolve: { 32 | extensions: ['.js', '.vue', '.jsx', 'css'], 33 | alias: { 34 | 'src': resolve(__dirname, '../src') 35 | } 36 | }, 37 | module: { 38 | rules: [ 39 | { 40 | test: /.jsx?$/, 41 | use: 'babel-loader', 42 | include: [ 43 | resolve(__dirname, '../node_modules/@material'), 44 | resolve(__dirname, '../src'), 45 | resolve(__dirname, '../test') 46 | ] 47 | }, 48 | { 49 | test: /\.vue$/, 50 | loader: 'vue-loader', 51 | options: { 52 | loaders: vueLoaders, 53 | postcss: [require('postcss-cssnext')()] 54 | } 55 | } 56 | ] 57 | }, 58 | plugins 59 | } 60 | -------------------------------------------------------------------------------- /build/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin') 5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 6 | const DashboardPlugin = require('webpack-dashboard/plugin') 7 | const base = require('./webpack.config.base') 8 | const { resolve, join } = require('path') 9 | const { existsSync } = require('fs') 10 | const { 11 | dllName, 12 | logError, 13 | red, 14 | vueLoaders 15 | } = require('./utils') 16 | 17 | const rootDir = resolve(__dirname, '../test') 18 | const buildPath = resolve(rootDir, 'dist') 19 | 20 | if (!existsSync(join(buildPath, dllName) + '.dll.js')) { 21 | logError(red('The DLL manifest is missing. Please run `npm run build:dll` (Quit this with `q`)')) 22 | process.exit(1) 23 | } 24 | 25 | const dllManifest = require( 26 | join(buildPath, dllName) + '.json' 27 | ) 28 | 29 | module.exports = merge(base, { 30 | entry: { 31 | tests: resolve(rootDir, 'visual.js') 32 | }, 33 | output: { 34 | path: buildPath, 35 | filename: '[name].js', 36 | chunkFilename: '[id].js' 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /.scss$/, 42 | loader: vueLoaders.scss, 43 | include: [ 44 | resolve(__dirname, '../node_modules/@material'), 45 | resolve(__dirname, '../src') 46 | ] 47 | } 48 | ] 49 | }, 50 | plugins: [ 51 | new webpack.DllReferencePlugin({ 52 | context: join(__dirname, '..'), 53 | manifest: dllManifest 54 | }), 55 | new HtmlWebpackPlugin({ 56 | chunkSortMode: 'dependency' 57 | }), 58 | new AddAssetHtmlPlugin({ 59 | filepath: require.resolve( 60 | join(buildPath, dllName) + '.dll.js' 61 | ) 62 | }), 63 | new webpack.optimize.CommonsChunkPlugin({ 64 | name: 'vendor', 65 | minChunks (module, count) { 66 | return ( 67 | module.resource && 68 | /\.js$/.test(module.resource) && 69 | module.resource.indexOf(join(__dirname, '../node_modules/')) === 0 70 | ) 71 | } 72 | }), 73 | new webpack.optimize.CommonsChunkPlugin({ 74 | name: 'manifest', 75 | chunks: ['vendor'] 76 | }), 77 | new DashboardPlugin(), 78 | new BundleAnalyzerPlugin({ 79 | analyzerMode: 'static', 80 | openAnalyzer: false, 81 | reportFilename: resolve(__dirname, `../reports/${process.env.NODE_ENV}.html`) 82 | }) 83 | ], 84 | devtool: '#eval-source-map', 85 | devServer: { 86 | inline: true, 87 | stats: { 88 | colors: true, 89 | chunks: false, 90 | cached: false 91 | }, 92 | contentBase: buildPath 93 | }, 94 | performance: { 95 | hints: false 96 | } 97 | }) 98 | -------------------------------------------------------------------------------- /build/webpack.config.dll.js: -------------------------------------------------------------------------------- 1 | const { resolve, join } = require('path') 2 | const webpack = require('webpack') 3 | const pkg = require('../package.json') 4 | 5 | const rootDir = resolve(__dirname, '../test') 6 | const buildPath = resolve(rootDir, 'dist') 7 | 8 | const entry = {} 9 | entry[pkg.dllPlugin.name] = pkg.dllPlugin.include 10 | 11 | module.exports = { 12 | devtool: '#source-map', 13 | entry, 14 | output: { 15 | path: buildPath, 16 | filename: '[name].dll.js', 17 | library: '[name]' 18 | }, 19 | plugins: [ 20 | new webpack.DllPlugin({ 21 | name: '[name]', 22 | path: join(buildPath, '[name].json') 23 | }) 24 | ], 25 | performance: { 26 | hints: false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dist/v-wechat-auth.common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * v-wechat-auth v1.0.0 3 | * (c) 2018 fengjun.chen 4 | * Released under the MIT License. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 10 | 11 | 12 | 13 | 14 | 15 | function createCommonjsModule(fn, module) { 16 | return module = { exports: {} }, fn(module, module.exports), module.exports; 17 | } 18 | 19 | var punycode = createCommonjsModule(function (module, exports) { 20 | /*! https://mths.be/punycode v1.3.2 by @mathias */ 21 | (function(root) { 22 | 23 | /** Detect free variables */ 24 | var freeExports = 'object' == 'object' && exports && 25 | !exports.nodeType && exports; 26 | var freeModule = 'object' == 'object' && module && 27 | !module.nodeType && module; 28 | var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal; 29 | if ( 30 | freeGlobal.global === freeGlobal || 31 | freeGlobal.window === freeGlobal || 32 | freeGlobal.self === freeGlobal 33 | ) { 34 | root = freeGlobal; 35 | } 36 | 37 | /** 38 | * The `punycode` object. 39 | * @name punycode 40 | * @type Object 41 | */ 42 | var punycode, 43 | 44 | /** Highest positive signed 32-bit float value */ 45 | maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 46 | 47 | /** Bootstring parameters */ 48 | base = 36, 49 | tMin = 1, 50 | tMax = 26, 51 | skew = 38, 52 | damp = 700, 53 | initialBias = 72, 54 | initialN = 128, // 0x80 55 | delimiter = '-', // '\x2D' 56 | 57 | /** Regular expressions */ 58 | regexPunycode = /^xn--/, 59 | regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars 60 | regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators 61 | 62 | /** Error messages */ 63 | errors = { 64 | 'overflow': 'Overflow: input needs wider integers to process', 65 | 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 66 | 'invalid-input': 'Invalid input' 67 | }, 68 | 69 | /** Convenience shortcuts */ 70 | baseMinusTMin = base - tMin, 71 | floor = Math.floor, 72 | stringFromCharCode = String.fromCharCode, 73 | 74 | /** Temporary variable */ 75 | key; 76 | 77 | /*--------------------------------------------------------------------------*/ 78 | 79 | /** 80 | * A generic error utility function. 81 | * @private 82 | * @param {String} type The error type. 83 | * @returns {Error} Throws a `RangeError` with the applicable error message. 84 | */ 85 | function error(type) { 86 | throw RangeError(errors[type]); 87 | } 88 | 89 | /** 90 | * A generic `Array#map` utility function. 91 | * @private 92 | * @param {Array} array The array to iterate over. 93 | * @param {Function} callback The function that gets called for every array 94 | * item. 95 | * @returns {Array} A new array of values returned by the callback function. 96 | */ 97 | function map(array, fn) { 98 | var length = array.length; 99 | var result = []; 100 | while (length--) { 101 | result[length] = fn(array[length]); 102 | } 103 | return result; 104 | } 105 | 106 | /** 107 | * A simple `Array#map`-like wrapper to work with domain name strings or email 108 | * addresses. 109 | * @private 110 | * @param {String} domain The domain name or email address. 111 | * @param {Function} callback The function that gets called for every 112 | * character. 113 | * @returns {Array} A new string of characters returned by the callback 114 | * function. 115 | */ 116 | function mapDomain(string, fn) { 117 | var parts = string.split('@'); 118 | var result = ''; 119 | if (parts.length > 1) { 120 | // In email addresses, only the domain name should be punycoded. Leave 121 | // the local part (i.e. everything up to `@`) intact. 122 | result = parts[0] + '@'; 123 | string = parts[1]; 124 | } 125 | // Avoid `split(regex)` for IE8 compatibility. See #17. 126 | string = string.replace(regexSeparators, '\x2E'); 127 | var labels = string.split('.'); 128 | var encoded = map(labels, fn).join('.'); 129 | return result + encoded; 130 | } 131 | 132 | /** 133 | * Creates an array containing the numeric code points of each Unicode 134 | * character in the string. While JavaScript uses UCS-2 internally, 135 | * this function will convert a pair of surrogate halves (each of which 136 | * UCS-2 exposes as separate characters) into a single code point, 137 | * matching UTF-16. 138 | * @see `punycode.ucs2.encode` 139 | * @see 140 | * @memberOf punycode.ucs2 141 | * @name decode 142 | * @param {String} string The Unicode input string (UCS-2). 143 | * @returns {Array} The new array of code points. 144 | */ 145 | function ucs2decode(string) { 146 | var output = [], 147 | counter = 0, 148 | length = string.length, 149 | value, 150 | extra; 151 | while (counter < length) { 152 | value = string.charCodeAt(counter++); 153 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 154 | // high surrogate, and there is a next character 155 | extra = string.charCodeAt(counter++); 156 | if ((extra & 0xFC00) == 0xDC00) { // low surrogate 157 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 158 | } else { 159 | // unmatched surrogate; only append this code unit, in case the next 160 | // code unit is the high surrogate of a surrogate pair 161 | output.push(value); 162 | counter--; 163 | } 164 | } else { 165 | output.push(value); 166 | } 167 | } 168 | return output; 169 | } 170 | 171 | /** 172 | * Creates a string based on an array of numeric code points. 173 | * @see `punycode.ucs2.decode` 174 | * @memberOf punycode.ucs2 175 | * @name encode 176 | * @param {Array} codePoints The array of numeric code points. 177 | * @returns {String} The new Unicode string (UCS-2). 178 | */ 179 | function ucs2encode(array) { 180 | return map(array, function(value) { 181 | var output = ''; 182 | if (value > 0xFFFF) { 183 | value -= 0x10000; 184 | output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); 185 | value = 0xDC00 | value & 0x3FF; 186 | } 187 | output += stringFromCharCode(value); 188 | return output; 189 | }).join(''); 190 | } 191 | 192 | /** 193 | * Converts a basic code point into a digit/integer. 194 | * @see `digitToBasic()` 195 | * @private 196 | * @param {Number} codePoint The basic numeric code point value. 197 | * @returns {Number} The numeric value of a basic code point (for use in 198 | * representing integers) in the range `0` to `base - 1`, or `base` if 199 | * the code point does not represent a value. 200 | */ 201 | function basicToDigit(codePoint) { 202 | if (codePoint - 48 < 10) { 203 | return codePoint - 22; 204 | } 205 | if (codePoint - 65 < 26) { 206 | return codePoint - 65; 207 | } 208 | if (codePoint - 97 < 26) { 209 | return codePoint - 97; 210 | } 211 | return base; 212 | } 213 | 214 | /** 215 | * Converts a digit/integer into a basic code point. 216 | * @see `basicToDigit()` 217 | * @private 218 | * @param {Number} digit The numeric value of a basic code point. 219 | * @returns {Number} The basic code point whose value (when used for 220 | * representing integers) is `digit`, which needs to be in the range 221 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 222 | * used; else, the lowercase form is used. The behavior is undefined 223 | * if `flag` is non-zero and `digit` has no uppercase form. 224 | */ 225 | function digitToBasic(digit, flag) { 226 | // 0..25 map to ASCII a..z or A..Z 227 | // 26..35 map to ASCII 0..9 228 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 229 | } 230 | 231 | /** 232 | * Bias adaptation function as per section 3.4 of RFC 3492. 233 | * http://tools.ietf.org/html/rfc3492#section-3.4 234 | * @private 235 | */ 236 | function adapt(delta, numPoints, firstTime) { 237 | var k = 0; 238 | delta = firstTime ? floor(delta / damp) : delta >> 1; 239 | delta += floor(delta / numPoints); 240 | for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 241 | delta = floor(delta / baseMinusTMin); 242 | } 243 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 244 | } 245 | 246 | /** 247 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode 248 | * symbols. 249 | * @memberOf punycode 250 | * @param {String} input The Punycode string of ASCII-only symbols. 251 | * @returns {String} The resulting string of Unicode symbols. 252 | */ 253 | function decode(input) { 254 | // Don't use UCS-2 255 | var output = [], 256 | inputLength = input.length, 257 | out, 258 | i = 0, 259 | n = initialN, 260 | bias = initialBias, 261 | basic, 262 | j, 263 | index, 264 | oldi, 265 | w, 266 | k, 267 | digit, 268 | t, 269 | /** Cached calculation results */ 270 | baseMinusT; 271 | 272 | // Handle the basic code points: let `basic` be the number of input code 273 | // points before the last delimiter, or `0` if there is none, then copy 274 | // the first basic code points to the output. 275 | 276 | basic = input.lastIndexOf(delimiter); 277 | if (basic < 0) { 278 | basic = 0; 279 | } 280 | 281 | for (j = 0; j < basic; ++j) { 282 | // if it's not a basic code point 283 | if (input.charCodeAt(j) >= 0x80) { 284 | error('not-basic'); 285 | } 286 | output.push(input.charCodeAt(j)); 287 | } 288 | 289 | // Main decoding loop: start just after the last delimiter if any basic code 290 | // points were copied; start at the beginning otherwise. 291 | 292 | for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 293 | 294 | // `index` is the index of the next character to be consumed. 295 | // Decode a generalized variable-length integer into `delta`, 296 | // which gets added to `i`. The overflow checking is easier 297 | // if we increase `i` as we go, then subtract off its starting 298 | // value at the end to obtain `delta`. 299 | for (oldi = i, w = 1, k = base; /* no condition */; k += base) { 300 | 301 | if (index >= inputLength) { 302 | error('invalid-input'); 303 | } 304 | 305 | digit = basicToDigit(input.charCodeAt(index++)); 306 | 307 | if (digit >= base || digit > floor((maxInt - i) / w)) { 308 | error('overflow'); 309 | } 310 | 311 | i += digit * w; 312 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 313 | 314 | if (digit < t) { 315 | break; 316 | } 317 | 318 | baseMinusT = base - t; 319 | if (w > floor(maxInt / baseMinusT)) { 320 | error('overflow'); 321 | } 322 | 323 | w *= baseMinusT; 324 | 325 | } 326 | 327 | out = output.length + 1; 328 | bias = adapt(i - oldi, out, oldi == 0); 329 | 330 | // `i` was supposed to wrap around from `out` to `0`, 331 | // incrementing `n` each time, so we'll fix that now: 332 | if (floor(i / out) > maxInt - n) { 333 | error('overflow'); 334 | } 335 | 336 | n += floor(i / out); 337 | i %= out; 338 | 339 | // Insert `n` at position `i` of the output 340 | output.splice(i++, 0, n); 341 | 342 | } 343 | 344 | return ucs2encode(output); 345 | } 346 | 347 | /** 348 | * Converts a string of Unicode symbols (e.g. a domain name label) to a 349 | * Punycode string of ASCII-only symbols. 350 | * @memberOf punycode 351 | * @param {String} input The string of Unicode symbols. 352 | * @returns {String} The resulting Punycode string of ASCII-only symbols. 353 | */ 354 | function encode(input) { 355 | var n, 356 | delta, 357 | handledCPCount, 358 | basicLength, 359 | bias, 360 | j, 361 | m, 362 | q, 363 | k, 364 | t, 365 | currentValue, 366 | output = [], 367 | /** `inputLength` will hold the number of code points in `input`. */ 368 | inputLength, 369 | /** Cached calculation results */ 370 | handledCPCountPlusOne, 371 | baseMinusT, 372 | qMinusT; 373 | 374 | // Convert the input in UCS-2 to Unicode 375 | input = ucs2decode(input); 376 | 377 | // Cache the length 378 | inputLength = input.length; 379 | 380 | // Initialize the state 381 | n = initialN; 382 | delta = 0; 383 | bias = initialBias; 384 | 385 | // Handle the basic code points 386 | for (j = 0; j < inputLength; ++j) { 387 | currentValue = input[j]; 388 | if (currentValue < 0x80) { 389 | output.push(stringFromCharCode(currentValue)); 390 | } 391 | } 392 | 393 | handledCPCount = basicLength = output.length; 394 | 395 | // `handledCPCount` is the number of code points that have been handled; 396 | // `basicLength` is the number of basic code points. 397 | 398 | // Finish the basic string - if it is not empty - with a delimiter 399 | if (basicLength) { 400 | output.push(delimiter); 401 | } 402 | 403 | // Main encoding loop: 404 | while (handledCPCount < inputLength) { 405 | 406 | // All non-basic code points < n have been handled already. Find the next 407 | // larger one: 408 | for (m = maxInt, j = 0; j < inputLength; ++j) { 409 | currentValue = input[j]; 410 | if (currentValue >= n && currentValue < m) { 411 | m = currentValue; 412 | } 413 | } 414 | 415 | // Increase `delta` enough to advance the decoder's state to , 416 | // but guard against overflow 417 | handledCPCountPlusOne = handledCPCount + 1; 418 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 419 | error('overflow'); 420 | } 421 | 422 | delta += (m - n) * handledCPCountPlusOne; 423 | n = m; 424 | 425 | for (j = 0; j < inputLength; ++j) { 426 | currentValue = input[j]; 427 | 428 | if (currentValue < n && ++delta > maxInt) { 429 | error('overflow'); 430 | } 431 | 432 | if (currentValue == n) { 433 | // Represent delta as a generalized variable-length integer 434 | for (q = delta, k = base; /* no condition */; k += base) { 435 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 436 | if (q < t) { 437 | break; 438 | } 439 | qMinusT = q - t; 440 | baseMinusT = base - t; 441 | output.push( 442 | stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 443 | ); 444 | q = floor(qMinusT / baseMinusT); 445 | } 446 | 447 | output.push(stringFromCharCode(digitToBasic(q, 0))); 448 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 449 | delta = 0; 450 | ++handledCPCount; 451 | } 452 | } 453 | 454 | ++delta; 455 | ++n; 456 | 457 | } 458 | return output.join(''); 459 | } 460 | 461 | /** 462 | * Converts a Punycode string representing a domain name or an email address 463 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e. 464 | * it doesn't matter if you call it on a string that has already been 465 | * converted to Unicode. 466 | * @memberOf punycode 467 | * @param {String} input The Punycoded domain name or email address to 468 | * convert to Unicode. 469 | * @returns {String} The Unicode representation of the given Punycode 470 | * string. 471 | */ 472 | function toUnicode(input) { 473 | return mapDomain(input, function(string) { 474 | return regexPunycode.test(string) 475 | ? decode(string.slice(4).toLowerCase()) 476 | : string; 477 | }); 478 | } 479 | 480 | /** 481 | * Converts a Unicode string representing a domain name or an email address to 482 | * Punycode. Only the non-ASCII parts of the domain name will be converted, 483 | * i.e. it doesn't matter if you call it with a domain that's already in 484 | * ASCII. 485 | * @memberOf punycode 486 | * @param {String} input The domain name or email address to convert, as a 487 | * Unicode string. 488 | * @returns {String} The Punycode representation of the given domain name or 489 | * email address. 490 | */ 491 | function toASCII(input) { 492 | return mapDomain(input, function(string) { 493 | return regexNonASCII.test(string) 494 | ? 'xn--' + encode(string) 495 | : string; 496 | }); 497 | } 498 | 499 | /*--------------------------------------------------------------------------*/ 500 | 501 | /** Define the public API */ 502 | punycode = { 503 | /** 504 | * A string representing the current Punycode.js version number. 505 | * @memberOf punycode 506 | * @type String 507 | */ 508 | 'version': '1.3.2', 509 | /** 510 | * An object of methods to convert from JavaScript's internal character 511 | * representation (UCS-2) to Unicode code points, and back. 512 | * @see 513 | * @memberOf punycode 514 | * @type Object 515 | */ 516 | 'ucs2': { 517 | 'decode': ucs2decode, 518 | 'encode': ucs2encode 519 | }, 520 | 'decode': decode, 521 | 'encode': encode, 522 | 'toASCII': toASCII, 523 | 'toUnicode': toUnicode 524 | }; 525 | 526 | /** Expose `punycode` */ 527 | // Some AMD build optimizers, like r.js, check for specific condition patterns 528 | // like the following: 529 | if ( 530 | typeof undefined == 'function' && 531 | typeof undefined.amd == 'object' && 532 | undefined.amd 533 | ) { 534 | undefined('punycode', function() { 535 | return punycode; 536 | }); 537 | } else if (freeExports && freeModule) { 538 | if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ 539 | freeModule.exports = punycode; 540 | } else { // in Narwhal or RingoJS v0.7.0- 541 | for (key in punycode) { 542 | punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); 543 | } 544 | } 545 | } else { // in Rhino or a web browser 546 | root.punycode = punycode; 547 | } 548 | 549 | }(commonjsGlobal)); 550 | }); 551 | 552 | var util = { 553 | isString: function(arg) { 554 | return typeof(arg) === 'string'; 555 | }, 556 | isObject: function(arg) { 557 | return typeof(arg) === 'object' && arg !== null; 558 | }, 559 | isNull: function(arg) { 560 | return arg === null; 561 | }, 562 | isNullOrUndefined: function(arg) { 563 | return arg == null; 564 | } 565 | }; 566 | 567 | // Copyright Joyent, Inc. and other Node contributors. 568 | // 569 | // Permission is hereby granted, free of charge, to any person obtaining a 570 | // copy of this software and associated documentation files (the 571 | // "Software"), to deal in the Software without restriction, including 572 | // without limitation the rights to use, copy, modify, merge, publish, 573 | // distribute, sublicense, and/or sell copies of the Software, and to permit 574 | // persons to whom the Software is furnished to do so, subject to the 575 | // following conditions: 576 | // 577 | // The above copyright notice and this permission notice shall be included 578 | // in all copies or substantial portions of the Software. 579 | // 580 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 581 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 582 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 583 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 584 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 585 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 586 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 587 | 588 | // If obj.hasOwnProperty has been overridden, then calling 589 | // obj.hasOwnProperty(prop) will break. 590 | // See: https://github.com/joyent/node/issues/1707 591 | function hasOwnProperty(obj, prop) { 592 | return Object.prototype.hasOwnProperty.call(obj, prop); 593 | } 594 | 595 | var decode = function(qs, sep, eq, options) { 596 | sep = sep || '&'; 597 | eq = eq || '='; 598 | var obj = {}; 599 | 600 | if (typeof qs !== 'string' || qs.length === 0) { 601 | return obj; 602 | } 603 | 604 | var regexp = /\+/g; 605 | qs = qs.split(sep); 606 | 607 | var maxKeys = 1000; 608 | if (options && typeof options.maxKeys === 'number') { 609 | maxKeys = options.maxKeys; 610 | } 611 | 612 | var len = qs.length; 613 | // maxKeys <= 0 means that we should not limit keys count 614 | if (maxKeys > 0 && len > maxKeys) { 615 | len = maxKeys; 616 | } 617 | 618 | for (var i = 0; i < len; ++i) { 619 | var x = qs[i].replace(regexp, '%20'), 620 | idx = x.indexOf(eq), 621 | kstr, vstr, k, v; 622 | 623 | if (idx >= 0) { 624 | kstr = x.substr(0, idx); 625 | vstr = x.substr(idx + 1); 626 | } else { 627 | kstr = x; 628 | vstr = ''; 629 | } 630 | 631 | k = decodeURIComponent(kstr); 632 | v = decodeURIComponent(vstr); 633 | 634 | if (!hasOwnProperty(obj, k)) { 635 | obj[k] = v; 636 | } else if (Array.isArray(obj[k])) { 637 | obj[k].push(v); 638 | } else { 639 | obj[k] = [obj[k], v]; 640 | } 641 | } 642 | 643 | return obj; 644 | }; 645 | 646 | // Copyright Joyent, Inc. and other Node contributors. 647 | // 648 | // Permission is hereby granted, free of charge, to any person obtaining a 649 | // copy of this software and associated documentation files (the 650 | // "Software"), to deal in the Software without restriction, including 651 | // without limitation the rights to use, copy, modify, merge, publish, 652 | // distribute, sublicense, and/or sell copies of the Software, and to permit 653 | // persons to whom the Software is furnished to do so, subject to the 654 | // following conditions: 655 | // 656 | // The above copyright notice and this permission notice shall be included 657 | // in all copies or substantial portions of the Software. 658 | // 659 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 660 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 661 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 662 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 663 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 664 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 665 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 666 | 667 | var stringifyPrimitive = function(v) { 668 | switch (typeof v) { 669 | case 'string': 670 | return v; 671 | 672 | case 'boolean': 673 | return v ? 'true' : 'false'; 674 | 675 | case 'number': 676 | return isFinite(v) ? v : ''; 677 | 678 | default: 679 | return ''; 680 | } 681 | }; 682 | 683 | var encode = function(obj, sep, eq, name) { 684 | sep = sep || '&'; 685 | eq = eq || '='; 686 | if (obj === null) { 687 | obj = undefined; 688 | } 689 | 690 | if (typeof obj === 'object') { 691 | return Object.keys(obj).map(function(k) { 692 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 693 | if (Array.isArray(obj[k])) { 694 | return obj[k].map(function(v) { 695 | return ks + encodeURIComponent(stringifyPrimitive(v)); 696 | }).join(sep); 697 | } else { 698 | return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 699 | } 700 | }).join(sep); 701 | 702 | } 703 | 704 | if (!name) { return ''; } 705 | return encodeURIComponent(stringifyPrimitive(name)) + eq + 706 | encodeURIComponent(stringifyPrimitive(obj)); 707 | }; 708 | 709 | var querystring = createCommonjsModule(function (module, exports) { 710 | 'use strict'; 711 | 712 | exports.decode = exports.parse = decode; 713 | exports.encode = exports.stringify = encode; 714 | }); 715 | 716 | var parse = urlParse; 717 | var resolve = urlResolve; 718 | var resolveObject = urlResolveObject; 719 | var format = urlFormat; 720 | 721 | var Url_1 = Url; 722 | 723 | function Url() { 724 | this.protocol = null; 725 | this.slashes = null; 726 | this.auth = null; 727 | this.host = null; 728 | this.port = null; 729 | this.hostname = null; 730 | this.hash = null; 731 | this.search = null; 732 | this.query = null; 733 | this.pathname = null; 734 | this.path = null; 735 | this.href = null; 736 | } 737 | 738 | // Reference: RFC 3986, RFC 1808, RFC 2396 739 | 740 | // define these here so at least they only have to be 741 | // compiled once on the first module load. 742 | var protocolPattern = /^([a-z0-9.+-]+:)/i; 743 | var portPattern = /:[0-9]*$/; 744 | var simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; 745 | var delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t']; 746 | var unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims); 747 | var autoEscape = ['\''].concat(unwise); 748 | var nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape); 749 | var hostEndingChars = ['/', '?', '#']; 750 | var hostnameMaxLen = 255; 751 | var hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/; 752 | var hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/; 753 | var unsafeProtocol = { 754 | 'javascript': true, 755 | 'javascript:': true 756 | }; 757 | var hostlessProtocol = { 758 | 'javascript': true, 759 | 'javascript:': true 760 | }; 761 | var slashedProtocol = { 762 | 'http': true, 763 | 'https': true, 764 | 'ftp': true, 765 | 'gopher': true, 766 | 'file': true, 767 | 'http:': true, 768 | 'https:': true, 769 | 'ftp:': true, 770 | 'gopher:': true, 771 | 'file:': true 772 | }; 773 | 774 | function urlParse(url, parseQueryString, slashesDenoteHost) { 775 | if (url && util.isObject(url) && url instanceof Url) { return url; } 776 | 777 | var u = new Url; 778 | u.parse(url, parseQueryString, slashesDenoteHost); 779 | return u; 780 | } 781 | 782 | Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { 783 | var this$1 = this; 784 | 785 | if (!util.isString(url)) { 786 | throw new TypeError("Parameter 'url' must be a string, not " + typeof url); 787 | } 788 | 789 | // Copy chrome, IE, opera backslash-handling behavior. 790 | // Back slashes before the query string get converted to forward slashes 791 | // See: https://code.google.com/p/chromium/issues/detail?id=25916 792 | var queryIndex = url.indexOf('?'), 793 | splitter = 794 | (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', 795 | uSplit = url.split(splitter), 796 | slashRegex = /\\/g; 797 | uSplit[0] = uSplit[0].replace(slashRegex, '/'); 798 | url = uSplit.join(splitter); 799 | 800 | var rest = url; 801 | 802 | // trim before proceeding. 803 | // This is to support parse stuff like " http://foo.com \n" 804 | rest = rest.trim(); 805 | 806 | if (!slashesDenoteHost && url.split('#').length === 1) { 807 | // Try fast path regexp 808 | var simplePath = simplePathPattern.exec(rest); 809 | if (simplePath) { 810 | this.path = rest; 811 | this.href = rest; 812 | this.pathname = simplePath[1]; 813 | if (simplePath[2]) { 814 | this.search = simplePath[2]; 815 | if (parseQueryString) { 816 | this.query = querystring.parse(this.search.substr(1)); 817 | } else { 818 | this.query = this.search.substr(1); 819 | } 820 | } else if (parseQueryString) { 821 | this.search = ''; 822 | this.query = {}; 823 | } 824 | return this; 825 | } 826 | } 827 | 828 | var proto = protocolPattern.exec(rest); 829 | if (proto) { 830 | proto = proto[0]; 831 | var lowerProto = proto.toLowerCase(); 832 | this.protocol = lowerProto; 833 | rest = rest.substr(proto.length); 834 | } 835 | 836 | // figure out if it's got a host 837 | // user@server is *always* interpreted as a hostname, and url 838 | // resolution will treat //foo/bar as host=foo,path=bar because that's 839 | // how the browser resolves relative URLs. 840 | if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { 841 | var slashes = rest.substr(0, 2) === '//'; 842 | if (slashes && !(proto && hostlessProtocol[proto])) { 843 | rest = rest.substr(2); 844 | this.slashes = true; 845 | } 846 | } 847 | 848 | if (!hostlessProtocol[proto] && 849 | (slashes || (proto && !slashedProtocol[proto]))) { 850 | 851 | // there's a hostname. 852 | // the first instance of /, ?, ;, or # ends the host. 853 | // 854 | // If there is an @ in the hostname, then non-host chars *are* allowed 855 | // to the left of the last @ sign, unless some host-ending character 856 | // comes *before* the @-sign. 857 | // URLs are obnoxious. 858 | // 859 | // ex: 860 | // http://a@b@c/ => user:a@b host:c 861 | // http://a@b?@c => user:a host:c path:/?@c 862 | 863 | // v0.12 TODO(isaacs): This is not quite how Chrome does things. 864 | // Review our test case against browsers more comprehensively. 865 | 866 | // find the first instance of any hostEndingChars 867 | var hostEnd = -1; 868 | for (var i = 0; i < hostEndingChars.length; i++) { 869 | var hec = rest.indexOf(hostEndingChars[i]); 870 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 871 | { hostEnd = hec; } 872 | } 873 | 874 | // at this point, either we have an explicit point where the 875 | // auth portion cannot go past, or the last @ char is the decider. 876 | var auth, atSign; 877 | if (hostEnd === -1) { 878 | // atSign can be anywhere. 879 | atSign = rest.lastIndexOf('@'); 880 | } else { 881 | // atSign must be in auth portion. 882 | // http://a@b/c@d => host:b auth:a path:/c@d 883 | atSign = rest.lastIndexOf('@', hostEnd); 884 | } 885 | 886 | // Now we have a portion which is definitely the auth. 887 | // Pull that off. 888 | if (atSign !== -1) { 889 | auth = rest.slice(0, atSign); 890 | rest = rest.slice(atSign + 1); 891 | this.auth = decodeURIComponent(auth); 892 | } 893 | 894 | // the host is the remaining to the left of the first non-host char 895 | hostEnd = -1; 896 | for (var i = 0; i < nonHostChars.length; i++) { 897 | var hec = rest.indexOf(nonHostChars[i]); 898 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 899 | { hostEnd = hec; } 900 | } 901 | // if we still have not hit it, then the entire thing is a host. 902 | if (hostEnd === -1) 903 | { hostEnd = rest.length; } 904 | 905 | this.host = rest.slice(0, hostEnd); 906 | rest = rest.slice(hostEnd); 907 | 908 | // pull out port. 909 | this.parseHost(); 910 | 911 | // we've indicated that there is a hostname, 912 | // so even if it's empty, it has to be present. 913 | this.hostname = this.hostname || ''; 914 | 915 | // if hostname begins with [ and ends with ] 916 | // assume that it's an IPv6 address. 917 | var ipv6Hostname = this.hostname[0] === '[' && 918 | this.hostname[this.hostname.length - 1] === ']'; 919 | 920 | // validate a little. 921 | if (!ipv6Hostname) { 922 | var hostparts = this.hostname.split(/\./); 923 | for (var i = 0, l = hostparts.length; i < l; i++) { 924 | var part = hostparts[i]; 925 | if (!part) { continue; } 926 | if (!part.match(hostnamePartPattern)) { 927 | var newpart = ''; 928 | for (var j = 0, k = part.length; j < k; j++) { 929 | if (part.charCodeAt(j) > 127) { 930 | // we replace non-ASCII char with a temporary placeholder 931 | // we need this to make sure size of hostname is not 932 | // broken by replacing non-ASCII by nothing 933 | newpart += 'x'; 934 | } else { 935 | newpart += part[j]; 936 | } 937 | } 938 | // we test again with ASCII char only 939 | if (!newpart.match(hostnamePartPattern)) { 940 | var validParts = hostparts.slice(0, i); 941 | var notHost = hostparts.slice(i + 1); 942 | var bit = part.match(hostnamePartStart); 943 | if (bit) { 944 | validParts.push(bit[1]); 945 | notHost.unshift(bit[2]); 946 | } 947 | if (notHost.length) { 948 | rest = '/' + notHost.join('.') + rest; 949 | } 950 | this$1.hostname = validParts.join('.'); 951 | break; 952 | } 953 | } 954 | } 955 | } 956 | 957 | if (this.hostname.length > hostnameMaxLen) { 958 | this.hostname = ''; 959 | } else { 960 | // hostnames are always lower case. 961 | this.hostname = this.hostname.toLowerCase(); 962 | } 963 | 964 | if (!ipv6Hostname) { 965 | // IDNA Support: Returns a punycoded representation of "domain". 966 | // It only converts parts of the domain name that 967 | // have non-ASCII characters, i.e. it doesn't matter if 968 | // you call it with a domain that already is ASCII-only. 969 | this.hostname = punycode.toASCII(this.hostname); 970 | } 971 | 972 | var p = this.port ? ':' + this.port : ''; 973 | var h = this.hostname || ''; 974 | this.host = h + p; 975 | this.href += this.host; 976 | 977 | // strip [ and ] from the hostname 978 | // the host field still retains them, though 979 | if (ipv6Hostname) { 980 | this.hostname = this.hostname.substr(1, this.hostname.length - 2); 981 | if (rest[0] !== '/') { 982 | rest = '/' + rest; 983 | } 984 | } 985 | } 986 | 987 | // now rest is set to the post-host stuff. 988 | // chop off any delim chars. 989 | if (!unsafeProtocol[lowerProto]) { 990 | 991 | // First, make 100% sure that any "autoEscape" chars get 992 | // escaped, even if encodeURIComponent doesn't think they 993 | // need to be. 994 | for (var i = 0, l = autoEscape.length; i < l; i++) { 995 | var ae = autoEscape[i]; 996 | if (rest.indexOf(ae) === -1) 997 | { continue; } 998 | var esc = encodeURIComponent(ae); 999 | if (esc === ae) { 1000 | esc = escape(ae); 1001 | } 1002 | rest = rest.split(ae).join(esc); 1003 | } 1004 | } 1005 | 1006 | 1007 | // chop off from the tail first. 1008 | var hash = rest.indexOf('#'); 1009 | if (hash !== -1) { 1010 | // got a fragment string. 1011 | this.hash = rest.substr(hash); 1012 | rest = rest.slice(0, hash); 1013 | } 1014 | var qm = rest.indexOf('?'); 1015 | if (qm !== -1) { 1016 | this.search = rest.substr(qm); 1017 | this.query = rest.substr(qm + 1); 1018 | if (parseQueryString) { 1019 | this.query = querystring.parse(this.query); 1020 | } 1021 | rest = rest.slice(0, qm); 1022 | } else if (parseQueryString) { 1023 | // no query string, but parseQueryString still requested 1024 | this.search = ''; 1025 | this.query = {}; 1026 | } 1027 | if (rest) { this.pathname = rest; } 1028 | if (slashedProtocol[lowerProto] && 1029 | this.hostname && !this.pathname) { 1030 | this.pathname = '/'; 1031 | } 1032 | 1033 | //to support http.request 1034 | if (this.pathname || this.search) { 1035 | var p = this.pathname || ''; 1036 | var s = this.search || ''; 1037 | this.path = p + s; 1038 | } 1039 | 1040 | // finally, reconstruct the href based on what has been validated. 1041 | this.href = this.format(); 1042 | return this; 1043 | }; 1044 | 1045 | // format a parsed object into a url string 1046 | function urlFormat(obj) { 1047 | // ensure it's an object, and not a string url. 1048 | // If it's an obj, this is a no-op. 1049 | // this way, you can call url_format() on strings 1050 | // to clean up potentially wonky urls. 1051 | if (util.isString(obj)) { obj = urlParse(obj); } 1052 | if (!(obj instanceof Url)) { return Url.prototype.format.call(obj); } 1053 | return obj.format(); 1054 | } 1055 | 1056 | Url.prototype.format = function() { 1057 | var auth = this.auth || ''; 1058 | if (auth) { 1059 | auth = encodeURIComponent(auth); 1060 | auth = auth.replace(/%3A/i, ':'); 1061 | auth += '@'; 1062 | } 1063 | 1064 | var protocol = this.protocol || '', 1065 | pathname = this.pathname || '', 1066 | hash = this.hash || '', 1067 | host = false, 1068 | query = ''; 1069 | 1070 | if (this.host) { 1071 | host = auth + this.host; 1072 | } else if (this.hostname) { 1073 | host = auth + (this.hostname.indexOf(':') === -1 ? 1074 | this.hostname : 1075 | '[' + this.hostname + ']'); 1076 | if (this.port) { 1077 | host += ':' + this.port; 1078 | } 1079 | } 1080 | 1081 | if (this.query && 1082 | util.isObject(this.query) && 1083 | Object.keys(this.query).length) { 1084 | query = querystring.stringify(this.query); 1085 | } 1086 | 1087 | var search = this.search || (query && ('?' + query)) || ''; 1088 | 1089 | if (protocol && protocol.substr(-1) !== ':') { protocol += ':'; } 1090 | 1091 | // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. 1092 | // unless they had them to begin with. 1093 | if (this.slashes || 1094 | (!protocol || slashedProtocol[protocol]) && host !== false) { 1095 | host = '//' + (host || ''); 1096 | if (pathname && pathname.charAt(0) !== '/') { pathname = '/' + pathname; } 1097 | } else if (!host) { 1098 | host = ''; 1099 | } 1100 | 1101 | if (hash && hash.charAt(0) !== '#') { hash = '#' + hash; } 1102 | if (search && search.charAt(0) !== '?') { search = '?' + search; } 1103 | 1104 | pathname = pathname.replace(/[?#]/g, function(match) { 1105 | return encodeURIComponent(match); 1106 | }); 1107 | search = search.replace('#', '%23'); 1108 | 1109 | return protocol + host + pathname + search + hash; 1110 | }; 1111 | 1112 | function urlResolve(source, relative) { 1113 | return urlParse(source, false, true).resolve(relative); 1114 | } 1115 | 1116 | Url.prototype.resolve = function(relative) { 1117 | return this.resolveObject(urlParse(relative, false, true)).format(); 1118 | }; 1119 | 1120 | function urlResolveObject(source, relative) { 1121 | if (!source) { return relative; } 1122 | return urlParse(source, false, true).resolveObject(relative); 1123 | } 1124 | 1125 | Url.prototype.resolveObject = function(relative) { 1126 | var this$1 = this; 1127 | 1128 | if (util.isString(relative)) { 1129 | var rel = new Url(); 1130 | rel.parse(relative, false, true); 1131 | relative = rel; 1132 | } 1133 | 1134 | var result = new Url(); 1135 | var tkeys = Object.keys(this); 1136 | for (var tk = 0; tk < tkeys.length; tk++) { 1137 | var tkey = tkeys[tk]; 1138 | result[tkey] = this$1[tkey]; 1139 | } 1140 | 1141 | // hash is always overridden, no matter what. 1142 | // even href="" will remove it. 1143 | result.hash = relative.hash; 1144 | 1145 | // if the relative url is empty, then there's nothing left to do here. 1146 | if (relative.href === '') { 1147 | result.href = result.format(); 1148 | return result; 1149 | } 1150 | 1151 | // hrefs like //foo/bar always cut to the protocol. 1152 | if (relative.slashes && !relative.protocol) { 1153 | // take everything except the protocol from relative 1154 | var rkeys = Object.keys(relative); 1155 | for (var rk = 0; rk < rkeys.length; rk++) { 1156 | var rkey = rkeys[rk]; 1157 | if (rkey !== 'protocol') 1158 | { result[rkey] = relative[rkey]; } 1159 | } 1160 | 1161 | //urlParse appends trailing / to urls like http://www.example.com 1162 | if (slashedProtocol[result.protocol] && 1163 | result.hostname && !result.pathname) { 1164 | result.path = result.pathname = '/'; 1165 | } 1166 | 1167 | result.href = result.format(); 1168 | return result; 1169 | } 1170 | 1171 | if (relative.protocol && relative.protocol !== result.protocol) { 1172 | // if it's a known url protocol, then changing 1173 | // the protocol does weird things 1174 | // first, if it's not file:, then we MUST have a host, 1175 | // and if there was a path 1176 | // to begin with, then we MUST have a path. 1177 | // if it is file:, then the host is dropped, 1178 | // because that's known to be hostless. 1179 | // anything else is assumed to be absolute. 1180 | if (!slashedProtocol[relative.protocol]) { 1181 | var keys = Object.keys(relative); 1182 | for (var v = 0; v < keys.length; v++) { 1183 | var k = keys[v]; 1184 | result[k] = relative[k]; 1185 | } 1186 | result.href = result.format(); 1187 | return result; 1188 | } 1189 | 1190 | result.protocol = relative.protocol; 1191 | if (!relative.host && !hostlessProtocol[relative.protocol]) { 1192 | var relPath = (relative.pathname || '').split('/'); 1193 | while (relPath.length && !(relative.host = relPath.shift())){ } 1194 | if (!relative.host) { relative.host = ''; } 1195 | if (!relative.hostname) { relative.hostname = ''; } 1196 | if (relPath[0] !== '') { relPath.unshift(''); } 1197 | if (relPath.length < 2) { relPath.unshift(''); } 1198 | result.pathname = relPath.join('/'); 1199 | } else { 1200 | result.pathname = relative.pathname; 1201 | } 1202 | result.search = relative.search; 1203 | result.query = relative.query; 1204 | result.host = relative.host || ''; 1205 | result.auth = relative.auth; 1206 | result.hostname = relative.hostname || relative.host; 1207 | result.port = relative.port; 1208 | // to support http.request 1209 | if (result.pathname || result.search) { 1210 | var p = result.pathname || ''; 1211 | var s = result.search || ''; 1212 | result.path = p + s; 1213 | } 1214 | result.slashes = result.slashes || relative.slashes; 1215 | result.href = result.format(); 1216 | return result; 1217 | } 1218 | 1219 | var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), 1220 | isRelAbs = ( 1221 | relative.host || 1222 | relative.pathname && relative.pathname.charAt(0) === '/' 1223 | ), 1224 | mustEndAbs = (isRelAbs || isSourceAbs || 1225 | (result.host && relative.pathname)), 1226 | removeAllDots = mustEndAbs, 1227 | srcPath = result.pathname && result.pathname.split('/') || [], 1228 | relPath = relative.pathname && relative.pathname.split('/') || [], 1229 | psychotic = result.protocol && !slashedProtocol[result.protocol]; 1230 | 1231 | // if the url is a non-slashed url, then relative 1232 | // links like ../.. should be able 1233 | // to crawl up to the hostname, as well. This is strange. 1234 | // result.protocol has already been set by now. 1235 | // Later on, put the first path part into the host field. 1236 | if (psychotic) { 1237 | result.hostname = ''; 1238 | result.port = null; 1239 | if (result.host) { 1240 | if (srcPath[0] === '') { srcPath[0] = result.host; } 1241 | else { srcPath.unshift(result.host); } 1242 | } 1243 | result.host = ''; 1244 | if (relative.protocol) { 1245 | relative.hostname = null; 1246 | relative.port = null; 1247 | if (relative.host) { 1248 | if (relPath[0] === '') { relPath[0] = relative.host; } 1249 | else { relPath.unshift(relative.host); } 1250 | } 1251 | relative.host = null; 1252 | } 1253 | mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); 1254 | } 1255 | 1256 | if (isRelAbs) { 1257 | // it's absolute. 1258 | result.host = (relative.host || relative.host === '') ? 1259 | relative.host : result.host; 1260 | result.hostname = (relative.hostname || relative.hostname === '') ? 1261 | relative.hostname : result.hostname; 1262 | result.search = relative.search; 1263 | result.query = relative.query; 1264 | srcPath = relPath; 1265 | // fall through to the dot-handling below. 1266 | } else if (relPath.length) { 1267 | // it's relative 1268 | // throw away the existing file, and take the new path instead. 1269 | if (!srcPath) { srcPath = []; } 1270 | srcPath.pop(); 1271 | srcPath = srcPath.concat(relPath); 1272 | result.search = relative.search; 1273 | result.query = relative.query; 1274 | } else if (!util.isNullOrUndefined(relative.search)) { 1275 | // just pull out the search. 1276 | // like href='?foo'. 1277 | // Put this after the other two cases because it simplifies the booleans 1278 | if (psychotic) { 1279 | result.hostname = result.host = srcPath.shift(); 1280 | //occationaly the auth can get stuck only in host 1281 | //this especially happens in cases like 1282 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1283 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1284 | result.host.split('@') : false; 1285 | if (authInHost) { 1286 | result.auth = authInHost.shift(); 1287 | result.host = result.hostname = authInHost.shift(); 1288 | } 1289 | } 1290 | result.search = relative.search; 1291 | result.query = relative.query; 1292 | //to support http.request 1293 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1294 | result.path = (result.pathname ? result.pathname : '') + 1295 | (result.search ? result.search : ''); 1296 | } 1297 | result.href = result.format(); 1298 | return result; 1299 | } 1300 | 1301 | if (!srcPath.length) { 1302 | // no path at all. easy. 1303 | // we've already handled the other stuff above. 1304 | result.pathname = null; 1305 | //to support http.request 1306 | if (result.search) { 1307 | result.path = '/' + result.search; 1308 | } else { 1309 | result.path = null; 1310 | } 1311 | result.href = result.format(); 1312 | return result; 1313 | } 1314 | 1315 | // if a url ENDs in . or .., then it must get a trailing slash. 1316 | // however, if it ends in anything else non-slashy, 1317 | // then it must NOT get a trailing slash. 1318 | var last = srcPath.slice(-1)[0]; 1319 | var hasTrailingSlash = ( 1320 | (result.host || relative.host || srcPath.length > 1) && 1321 | (last === '.' || last === '..') || last === ''); 1322 | 1323 | // strip single dots, resolve double dots to parent dir 1324 | // if the path tries to go above the root, `up` ends up > 0 1325 | var up = 0; 1326 | for (var i = srcPath.length; i >= 0; i--) { 1327 | last = srcPath[i]; 1328 | if (last === '.') { 1329 | srcPath.splice(i, 1); 1330 | } else if (last === '..') { 1331 | srcPath.splice(i, 1); 1332 | up++; 1333 | } else if (up) { 1334 | srcPath.splice(i, 1); 1335 | up--; 1336 | } 1337 | } 1338 | 1339 | // if the path is allowed to go above the root, restore leading ..s 1340 | if (!mustEndAbs && !removeAllDots) { 1341 | for (; up--; up) { 1342 | srcPath.unshift('..'); 1343 | } 1344 | } 1345 | 1346 | if (mustEndAbs && srcPath[0] !== '' && 1347 | (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { 1348 | srcPath.unshift(''); 1349 | } 1350 | 1351 | if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { 1352 | srcPath.push(''); 1353 | } 1354 | 1355 | var isAbsolute = srcPath[0] === '' || 1356 | (srcPath[0] && srcPath[0].charAt(0) === '/'); 1357 | 1358 | // put the host back 1359 | if (psychotic) { 1360 | result.hostname = result.host = isAbsolute ? '' : 1361 | srcPath.length ? srcPath.shift() : ''; 1362 | //occationaly the auth can get stuck only in host 1363 | //this especially happens in cases like 1364 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1365 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1366 | result.host.split('@') : false; 1367 | if (authInHost) { 1368 | result.auth = authInHost.shift(); 1369 | result.host = result.hostname = authInHost.shift(); 1370 | } 1371 | } 1372 | 1373 | mustEndAbs = mustEndAbs || (result.host && srcPath.length); 1374 | 1375 | if (mustEndAbs && !isAbsolute) { 1376 | srcPath.unshift(''); 1377 | } 1378 | 1379 | if (!srcPath.length) { 1380 | result.pathname = null; 1381 | result.path = null; 1382 | } else { 1383 | result.pathname = srcPath.join('/'); 1384 | } 1385 | 1386 | //to support request.http 1387 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1388 | result.path = (result.pathname ? result.pathname : '') + 1389 | (result.search ? result.search : ''); 1390 | } 1391 | result.auth = relative.auth || result.auth; 1392 | result.slashes = result.slashes || relative.slashes; 1393 | result.href = result.format(); 1394 | return result; 1395 | }; 1396 | 1397 | Url.prototype.parseHost = function() { 1398 | var host = this.host; 1399 | var port = portPattern.exec(host); 1400 | if (port) { 1401 | port = port[0]; 1402 | if (port !== ':') { 1403 | this.port = port.substr(1); 1404 | } 1405 | host = host.substr(0, host.length - port.length); 1406 | } 1407 | if (host) { this.hostname = host; } 1408 | }; 1409 | 1410 | var url = { 1411 | parse: parse, 1412 | resolve: resolve, 1413 | resolveObject: resolveObject, 1414 | format: format, 1415 | Url: Url_1 1416 | }; 1417 | 1418 | function install (Vue, options) { 1419 | if (install.installed) { return } 1420 | install.installed = true; 1421 | 1422 | Object.defineProperty(Vue.prototype, '$wechatAuth', { 1423 | get: function get () { return this.$root._wechatAuth } 1424 | }); 1425 | 1426 | Vue.mixin({ 1427 | beforeCreate: function beforeCreate () { 1428 | if (this.$options.wechatAuth) { 1429 | this._wechatAuth = this.$options.wechatAuth; 1430 | } 1431 | } 1432 | }); 1433 | } 1434 | 1435 | var config = { 1436 | git: 'https://github.com/raychenfj/v-wechat-auth' 1437 | }; 1438 | 1439 | var requiredProps = ['appId', 'scope', 'authorize']; 1440 | var defaultOptions = { 1441 | autoRedirect: true, 1442 | state: '', 1443 | authorize: function authorize () { 1444 | console.error('should implement authorize method in options'); 1445 | } 1446 | }; 1447 | 1448 | function isFunction (fn) { 1449 | return fn && typeof fn === 'function' 1450 | } 1451 | 1452 | var WechatAuth = function WechatAuth (options) { 1453 | var this$1 = this; 1454 | 1455 | this.options = Object.assign(defaultOptions, options); 1456 | 1457 | requiredProps.forEach(function (prop) { 1458 | if (!this$1.options[prop]) { 1459 | console.error(("required property " + prop + " is missing in options, please visit " + (config.git) + " for more info.")); 1460 | } 1461 | }); 1462 | 1463 | this.user = null; 1464 | }; 1465 | 1466 | /** 1467 | * authorize 1468 | * @param {*} onSuccess 1469 | * @param {*} onFail 1470 | * @returns {Promise} 1471 | */ 1472 | WechatAuth.prototype.authorize = function authorize (onSuccess, onFail) { 1473 | var this$1 = this; 1474 | 1475 | var urlObj = url.parse(window.location.href, true); 1476 | 1477 | if (urlObj.query && !urlObj.query.code) { 1478 | // delete state in query in url 1479 | delete urlObj.query.state; 1480 | delete urlObj.search; 1481 | return this.redirect(url.format(urlObj)) 1482 | } 1483 | 1484 | // decorated success 1485 | var success = function (data) { 1486 | var user = this$1.onSuccess(data); 1487 | if (isFunction(onSuccess)) { 1488 | onSuccess(user); 1489 | } 1490 | return user 1491 | }; 1492 | 1493 | // decorated fail 1494 | var fail = function (e) { 1495 | this$1.onFail(e); 1496 | if (isFunction(onFail)) { 1497 | onFail(e); 1498 | } 1499 | }; 1500 | 1501 | try { 1502 | // if options.authorize use callback 1503 | var promise = this.options.authorize(urlObj.query.code, success, fail); 1504 | 1505 | // if options.authorize return promise 1506 | if (promise && promise instanceof Promise) { 1507 | return promise.then(success).catch(fail) 1508 | } 1509 | } catch (e) { 1510 | fail(e); 1511 | } 1512 | }; 1513 | 1514 | /** 1515 | * onSuccess 1516 | * @private 1517 | * @param {*} data 1518 | */ 1519 | WechatAuth.prototype.onSuccess = function onSuccess (data) { 1520 | if (!data.openid && this.options.autoRedirect) { 1521 | var urlObj = url.parse(window.location.href, true); 1522 | // delete code and state in query in url 1523 | delete urlObj.query.code; 1524 | delete urlObj.query.state; 1525 | delete urlObj.search; 1526 | return this.redirect(url.format(urlObj)) 1527 | } 1528 | this.user = data; 1529 | return this.user 1530 | }; 1531 | 1532 | /** 1533 | * onFail 1534 | * @private 1535 | * @param {*} e 1536 | */ 1537 | WechatAuth.prototype.onFail = function onFail (e) { 1538 | console.error('error occurs when authorize from back end'); 1539 | console.error(e); 1540 | }; 1541 | 1542 | /** 1543 | * redirect to wechat auth url 1544 | * @param {*} url 1545 | */ 1546 | WechatAuth.prototype.redirect = function redirect (url$$1) { 1547 | var options = this.options; 1548 | window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + (options.appId) + "&redirect_uri=" + (encodeURIComponent(url$$1)) + "&response_type=code&scope=" + (options.scope) + "&state=" + (options.state) + "#wechat_redirect"; 1549 | }; 1550 | 1551 | WechatAuth.install = install; 1552 | 1553 | module.exports = WechatAuth; 1554 | -------------------------------------------------------------------------------- /dist/v-wechat-auth.esm.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * v-wechat-auth v1.0.0 3 | * (c) 2018 fengjun.chen 4 | * Released under the MIT License. 5 | */ 6 | 7 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 8 | 9 | 10 | 11 | 12 | 13 | function createCommonjsModule(fn, module) { 14 | return module = { exports: {} }, fn(module, module.exports), module.exports; 15 | } 16 | 17 | var punycode = createCommonjsModule(function (module, exports) { 18 | /*! https://mths.be/punycode v1.3.2 by @mathias */ 19 | (function(root) { 20 | 21 | /** Detect free variables */ 22 | var freeExports = 'object' == 'object' && exports && 23 | !exports.nodeType && exports; 24 | var freeModule = 'object' == 'object' && module && 25 | !module.nodeType && module; 26 | var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal; 27 | if ( 28 | freeGlobal.global === freeGlobal || 29 | freeGlobal.window === freeGlobal || 30 | freeGlobal.self === freeGlobal 31 | ) { 32 | root = freeGlobal; 33 | } 34 | 35 | /** 36 | * The `punycode` object. 37 | * @name punycode 38 | * @type Object 39 | */ 40 | var punycode, 41 | 42 | /** Highest positive signed 32-bit float value */ 43 | maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 44 | 45 | /** Bootstring parameters */ 46 | base = 36, 47 | tMin = 1, 48 | tMax = 26, 49 | skew = 38, 50 | damp = 700, 51 | initialBias = 72, 52 | initialN = 128, // 0x80 53 | delimiter = '-', // '\x2D' 54 | 55 | /** Regular expressions */ 56 | regexPunycode = /^xn--/, 57 | regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars 58 | regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators 59 | 60 | /** Error messages */ 61 | errors = { 62 | 'overflow': 'Overflow: input needs wider integers to process', 63 | 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 64 | 'invalid-input': 'Invalid input' 65 | }, 66 | 67 | /** Convenience shortcuts */ 68 | baseMinusTMin = base - tMin, 69 | floor = Math.floor, 70 | stringFromCharCode = String.fromCharCode, 71 | 72 | /** Temporary variable */ 73 | key; 74 | 75 | /*--------------------------------------------------------------------------*/ 76 | 77 | /** 78 | * A generic error utility function. 79 | * @private 80 | * @param {String} type The error type. 81 | * @returns {Error} Throws a `RangeError` with the applicable error message. 82 | */ 83 | function error(type) { 84 | throw RangeError(errors[type]); 85 | } 86 | 87 | /** 88 | * A generic `Array#map` utility function. 89 | * @private 90 | * @param {Array} array The array to iterate over. 91 | * @param {Function} callback The function that gets called for every array 92 | * item. 93 | * @returns {Array} A new array of values returned by the callback function. 94 | */ 95 | function map(array, fn) { 96 | var length = array.length; 97 | var result = []; 98 | while (length--) { 99 | result[length] = fn(array[length]); 100 | } 101 | return result; 102 | } 103 | 104 | /** 105 | * A simple `Array#map`-like wrapper to work with domain name strings or email 106 | * addresses. 107 | * @private 108 | * @param {String} domain The domain name or email address. 109 | * @param {Function} callback The function that gets called for every 110 | * character. 111 | * @returns {Array} A new string of characters returned by the callback 112 | * function. 113 | */ 114 | function mapDomain(string, fn) { 115 | var parts = string.split('@'); 116 | var result = ''; 117 | if (parts.length > 1) { 118 | // In email addresses, only the domain name should be punycoded. Leave 119 | // the local part (i.e. everything up to `@`) intact. 120 | result = parts[0] + '@'; 121 | string = parts[1]; 122 | } 123 | // Avoid `split(regex)` for IE8 compatibility. See #17. 124 | string = string.replace(regexSeparators, '\x2E'); 125 | var labels = string.split('.'); 126 | var encoded = map(labels, fn).join('.'); 127 | return result + encoded; 128 | } 129 | 130 | /** 131 | * Creates an array containing the numeric code points of each Unicode 132 | * character in the string. While JavaScript uses UCS-2 internally, 133 | * this function will convert a pair of surrogate halves (each of which 134 | * UCS-2 exposes as separate characters) into a single code point, 135 | * matching UTF-16. 136 | * @see `punycode.ucs2.encode` 137 | * @see 138 | * @memberOf punycode.ucs2 139 | * @name decode 140 | * @param {String} string The Unicode input string (UCS-2). 141 | * @returns {Array} The new array of code points. 142 | */ 143 | function ucs2decode(string) { 144 | var output = [], 145 | counter = 0, 146 | length = string.length, 147 | value, 148 | extra; 149 | while (counter < length) { 150 | value = string.charCodeAt(counter++); 151 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 152 | // high surrogate, and there is a next character 153 | extra = string.charCodeAt(counter++); 154 | if ((extra & 0xFC00) == 0xDC00) { // low surrogate 155 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 156 | } else { 157 | // unmatched surrogate; only append this code unit, in case the next 158 | // code unit is the high surrogate of a surrogate pair 159 | output.push(value); 160 | counter--; 161 | } 162 | } else { 163 | output.push(value); 164 | } 165 | } 166 | return output; 167 | } 168 | 169 | /** 170 | * Creates a string based on an array of numeric code points. 171 | * @see `punycode.ucs2.decode` 172 | * @memberOf punycode.ucs2 173 | * @name encode 174 | * @param {Array} codePoints The array of numeric code points. 175 | * @returns {String} The new Unicode string (UCS-2). 176 | */ 177 | function ucs2encode(array) { 178 | return map(array, function(value) { 179 | var output = ''; 180 | if (value > 0xFFFF) { 181 | value -= 0x10000; 182 | output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); 183 | value = 0xDC00 | value & 0x3FF; 184 | } 185 | output += stringFromCharCode(value); 186 | return output; 187 | }).join(''); 188 | } 189 | 190 | /** 191 | * Converts a basic code point into a digit/integer. 192 | * @see `digitToBasic()` 193 | * @private 194 | * @param {Number} codePoint The basic numeric code point value. 195 | * @returns {Number} The numeric value of a basic code point (for use in 196 | * representing integers) in the range `0` to `base - 1`, or `base` if 197 | * the code point does not represent a value. 198 | */ 199 | function basicToDigit(codePoint) { 200 | if (codePoint - 48 < 10) { 201 | return codePoint - 22; 202 | } 203 | if (codePoint - 65 < 26) { 204 | return codePoint - 65; 205 | } 206 | if (codePoint - 97 < 26) { 207 | return codePoint - 97; 208 | } 209 | return base; 210 | } 211 | 212 | /** 213 | * Converts a digit/integer into a basic code point. 214 | * @see `basicToDigit()` 215 | * @private 216 | * @param {Number} digit The numeric value of a basic code point. 217 | * @returns {Number} The basic code point whose value (when used for 218 | * representing integers) is `digit`, which needs to be in the range 219 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 220 | * used; else, the lowercase form is used. The behavior is undefined 221 | * if `flag` is non-zero and `digit` has no uppercase form. 222 | */ 223 | function digitToBasic(digit, flag) { 224 | // 0..25 map to ASCII a..z or A..Z 225 | // 26..35 map to ASCII 0..9 226 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 227 | } 228 | 229 | /** 230 | * Bias adaptation function as per section 3.4 of RFC 3492. 231 | * http://tools.ietf.org/html/rfc3492#section-3.4 232 | * @private 233 | */ 234 | function adapt(delta, numPoints, firstTime) { 235 | var k = 0; 236 | delta = firstTime ? floor(delta / damp) : delta >> 1; 237 | delta += floor(delta / numPoints); 238 | for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 239 | delta = floor(delta / baseMinusTMin); 240 | } 241 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 242 | } 243 | 244 | /** 245 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode 246 | * symbols. 247 | * @memberOf punycode 248 | * @param {String} input The Punycode string of ASCII-only symbols. 249 | * @returns {String} The resulting string of Unicode symbols. 250 | */ 251 | function decode(input) { 252 | // Don't use UCS-2 253 | var output = [], 254 | inputLength = input.length, 255 | out, 256 | i = 0, 257 | n = initialN, 258 | bias = initialBias, 259 | basic, 260 | j, 261 | index, 262 | oldi, 263 | w, 264 | k, 265 | digit, 266 | t, 267 | /** Cached calculation results */ 268 | baseMinusT; 269 | 270 | // Handle the basic code points: let `basic` be the number of input code 271 | // points before the last delimiter, or `0` if there is none, then copy 272 | // the first basic code points to the output. 273 | 274 | basic = input.lastIndexOf(delimiter); 275 | if (basic < 0) { 276 | basic = 0; 277 | } 278 | 279 | for (j = 0; j < basic; ++j) { 280 | // if it's not a basic code point 281 | if (input.charCodeAt(j) >= 0x80) { 282 | error('not-basic'); 283 | } 284 | output.push(input.charCodeAt(j)); 285 | } 286 | 287 | // Main decoding loop: start just after the last delimiter if any basic code 288 | // points were copied; start at the beginning otherwise. 289 | 290 | for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 291 | 292 | // `index` is the index of the next character to be consumed. 293 | // Decode a generalized variable-length integer into `delta`, 294 | // which gets added to `i`. The overflow checking is easier 295 | // if we increase `i` as we go, then subtract off its starting 296 | // value at the end to obtain `delta`. 297 | for (oldi = i, w = 1, k = base; /* no condition */; k += base) { 298 | 299 | if (index >= inputLength) { 300 | error('invalid-input'); 301 | } 302 | 303 | digit = basicToDigit(input.charCodeAt(index++)); 304 | 305 | if (digit >= base || digit > floor((maxInt - i) / w)) { 306 | error('overflow'); 307 | } 308 | 309 | i += digit * w; 310 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 311 | 312 | if (digit < t) { 313 | break; 314 | } 315 | 316 | baseMinusT = base - t; 317 | if (w > floor(maxInt / baseMinusT)) { 318 | error('overflow'); 319 | } 320 | 321 | w *= baseMinusT; 322 | 323 | } 324 | 325 | out = output.length + 1; 326 | bias = adapt(i - oldi, out, oldi == 0); 327 | 328 | // `i` was supposed to wrap around from `out` to `0`, 329 | // incrementing `n` each time, so we'll fix that now: 330 | if (floor(i / out) > maxInt - n) { 331 | error('overflow'); 332 | } 333 | 334 | n += floor(i / out); 335 | i %= out; 336 | 337 | // Insert `n` at position `i` of the output 338 | output.splice(i++, 0, n); 339 | 340 | } 341 | 342 | return ucs2encode(output); 343 | } 344 | 345 | /** 346 | * Converts a string of Unicode symbols (e.g. a domain name label) to a 347 | * Punycode string of ASCII-only symbols. 348 | * @memberOf punycode 349 | * @param {String} input The string of Unicode symbols. 350 | * @returns {String} The resulting Punycode string of ASCII-only symbols. 351 | */ 352 | function encode(input) { 353 | var n, 354 | delta, 355 | handledCPCount, 356 | basicLength, 357 | bias, 358 | j, 359 | m, 360 | q, 361 | k, 362 | t, 363 | currentValue, 364 | output = [], 365 | /** `inputLength` will hold the number of code points in `input`. */ 366 | inputLength, 367 | /** Cached calculation results */ 368 | handledCPCountPlusOne, 369 | baseMinusT, 370 | qMinusT; 371 | 372 | // Convert the input in UCS-2 to Unicode 373 | input = ucs2decode(input); 374 | 375 | // Cache the length 376 | inputLength = input.length; 377 | 378 | // Initialize the state 379 | n = initialN; 380 | delta = 0; 381 | bias = initialBias; 382 | 383 | // Handle the basic code points 384 | for (j = 0; j < inputLength; ++j) { 385 | currentValue = input[j]; 386 | if (currentValue < 0x80) { 387 | output.push(stringFromCharCode(currentValue)); 388 | } 389 | } 390 | 391 | handledCPCount = basicLength = output.length; 392 | 393 | // `handledCPCount` is the number of code points that have been handled; 394 | // `basicLength` is the number of basic code points. 395 | 396 | // Finish the basic string - if it is not empty - with a delimiter 397 | if (basicLength) { 398 | output.push(delimiter); 399 | } 400 | 401 | // Main encoding loop: 402 | while (handledCPCount < inputLength) { 403 | 404 | // All non-basic code points < n have been handled already. Find the next 405 | // larger one: 406 | for (m = maxInt, j = 0; j < inputLength; ++j) { 407 | currentValue = input[j]; 408 | if (currentValue >= n && currentValue < m) { 409 | m = currentValue; 410 | } 411 | } 412 | 413 | // Increase `delta` enough to advance the decoder's state to , 414 | // but guard against overflow 415 | handledCPCountPlusOne = handledCPCount + 1; 416 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 417 | error('overflow'); 418 | } 419 | 420 | delta += (m - n) * handledCPCountPlusOne; 421 | n = m; 422 | 423 | for (j = 0; j < inputLength; ++j) { 424 | currentValue = input[j]; 425 | 426 | if (currentValue < n && ++delta > maxInt) { 427 | error('overflow'); 428 | } 429 | 430 | if (currentValue == n) { 431 | // Represent delta as a generalized variable-length integer 432 | for (q = delta, k = base; /* no condition */; k += base) { 433 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 434 | if (q < t) { 435 | break; 436 | } 437 | qMinusT = q - t; 438 | baseMinusT = base - t; 439 | output.push( 440 | stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 441 | ); 442 | q = floor(qMinusT / baseMinusT); 443 | } 444 | 445 | output.push(stringFromCharCode(digitToBasic(q, 0))); 446 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 447 | delta = 0; 448 | ++handledCPCount; 449 | } 450 | } 451 | 452 | ++delta; 453 | ++n; 454 | 455 | } 456 | return output.join(''); 457 | } 458 | 459 | /** 460 | * Converts a Punycode string representing a domain name or an email address 461 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e. 462 | * it doesn't matter if you call it on a string that has already been 463 | * converted to Unicode. 464 | * @memberOf punycode 465 | * @param {String} input The Punycoded domain name or email address to 466 | * convert to Unicode. 467 | * @returns {String} The Unicode representation of the given Punycode 468 | * string. 469 | */ 470 | function toUnicode(input) { 471 | return mapDomain(input, function(string) { 472 | return regexPunycode.test(string) 473 | ? decode(string.slice(4).toLowerCase()) 474 | : string; 475 | }); 476 | } 477 | 478 | /** 479 | * Converts a Unicode string representing a domain name or an email address to 480 | * Punycode. Only the non-ASCII parts of the domain name will be converted, 481 | * i.e. it doesn't matter if you call it with a domain that's already in 482 | * ASCII. 483 | * @memberOf punycode 484 | * @param {String} input The domain name or email address to convert, as a 485 | * Unicode string. 486 | * @returns {String} The Punycode representation of the given domain name or 487 | * email address. 488 | */ 489 | function toASCII(input) { 490 | return mapDomain(input, function(string) { 491 | return regexNonASCII.test(string) 492 | ? 'xn--' + encode(string) 493 | : string; 494 | }); 495 | } 496 | 497 | /*--------------------------------------------------------------------------*/ 498 | 499 | /** Define the public API */ 500 | punycode = { 501 | /** 502 | * A string representing the current Punycode.js version number. 503 | * @memberOf punycode 504 | * @type String 505 | */ 506 | 'version': '1.3.2', 507 | /** 508 | * An object of methods to convert from JavaScript's internal character 509 | * representation (UCS-2) to Unicode code points, and back. 510 | * @see 511 | * @memberOf punycode 512 | * @type Object 513 | */ 514 | 'ucs2': { 515 | 'decode': ucs2decode, 516 | 'encode': ucs2encode 517 | }, 518 | 'decode': decode, 519 | 'encode': encode, 520 | 'toASCII': toASCII, 521 | 'toUnicode': toUnicode 522 | }; 523 | 524 | /** Expose `punycode` */ 525 | // Some AMD build optimizers, like r.js, check for specific condition patterns 526 | // like the following: 527 | if ( 528 | typeof undefined == 'function' && 529 | typeof undefined.amd == 'object' && 530 | undefined.amd 531 | ) { 532 | undefined('punycode', function() { 533 | return punycode; 534 | }); 535 | } else if (freeExports && freeModule) { 536 | if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ 537 | freeModule.exports = punycode; 538 | } else { // in Narwhal or RingoJS v0.7.0- 539 | for (key in punycode) { 540 | punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); 541 | } 542 | } 543 | } else { // in Rhino or a web browser 544 | root.punycode = punycode; 545 | } 546 | 547 | }(commonjsGlobal)); 548 | }); 549 | 550 | var util = { 551 | isString: function(arg) { 552 | return typeof(arg) === 'string'; 553 | }, 554 | isObject: function(arg) { 555 | return typeof(arg) === 'object' && arg !== null; 556 | }, 557 | isNull: function(arg) { 558 | return arg === null; 559 | }, 560 | isNullOrUndefined: function(arg) { 561 | return arg == null; 562 | } 563 | }; 564 | 565 | // Copyright Joyent, Inc. and other Node contributors. 566 | // 567 | // Permission is hereby granted, free of charge, to any person obtaining a 568 | // copy of this software and associated documentation files (the 569 | // "Software"), to deal in the Software without restriction, including 570 | // without limitation the rights to use, copy, modify, merge, publish, 571 | // distribute, sublicense, and/or sell copies of the Software, and to permit 572 | // persons to whom the Software is furnished to do so, subject to the 573 | // following conditions: 574 | // 575 | // The above copyright notice and this permission notice shall be included 576 | // in all copies or substantial portions of the Software. 577 | // 578 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 579 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 580 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 581 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 582 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 583 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 584 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 585 | 586 | // If obj.hasOwnProperty has been overridden, then calling 587 | // obj.hasOwnProperty(prop) will break. 588 | // See: https://github.com/joyent/node/issues/1707 589 | function hasOwnProperty(obj, prop) { 590 | return Object.prototype.hasOwnProperty.call(obj, prop); 591 | } 592 | 593 | var decode = function(qs, sep, eq, options) { 594 | sep = sep || '&'; 595 | eq = eq || '='; 596 | var obj = {}; 597 | 598 | if (typeof qs !== 'string' || qs.length === 0) { 599 | return obj; 600 | } 601 | 602 | var regexp = /\+/g; 603 | qs = qs.split(sep); 604 | 605 | var maxKeys = 1000; 606 | if (options && typeof options.maxKeys === 'number') { 607 | maxKeys = options.maxKeys; 608 | } 609 | 610 | var len = qs.length; 611 | // maxKeys <= 0 means that we should not limit keys count 612 | if (maxKeys > 0 && len > maxKeys) { 613 | len = maxKeys; 614 | } 615 | 616 | for (var i = 0; i < len; ++i) { 617 | var x = qs[i].replace(regexp, '%20'), 618 | idx = x.indexOf(eq), 619 | kstr, vstr, k, v; 620 | 621 | if (idx >= 0) { 622 | kstr = x.substr(0, idx); 623 | vstr = x.substr(idx + 1); 624 | } else { 625 | kstr = x; 626 | vstr = ''; 627 | } 628 | 629 | k = decodeURIComponent(kstr); 630 | v = decodeURIComponent(vstr); 631 | 632 | if (!hasOwnProperty(obj, k)) { 633 | obj[k] = v; 634 | } else if (Array.isArray(obj[k])) { 635 | obj[k].push(v); 636 | } else { 637 | obj[k] = [obj[k], v]; 638 | } 639 | } 640 | 641 | return obj; 642 | }; 643 | 644 | // Copyright Joyent, Inc. and other Node contributors. 645 | // 646 | // Permission is hereby granted, free of charge, to any person obtaining a 647 | // copy of this software and associated documentation files (the 648 | // "Software"), to deal in the Software without restriction, including 649 | // without limitation the rights to use, copy, modify, merge, publish, 650 | // distribute, sublicense, and/or sell copies of the Software, and to permit 651 | // persons to whom the Software is furnished to do so, subject to the 652 | // following conditions: 653 | // 654 | // The above copyright notice and this permission notice shall be included 655 | // in all copies or substantial portions of the Software. 656 | // 657 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 658 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 659 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 660 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 661 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 662 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 663 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 664 | 665 | var stringifyPrimitive = function(v) { 666 | switch (typeof v) { 667 | case 'string': 668 | return v; 669 | 670 | case 'boolean': 671 | return v ? 'true' : 'false'; 672 | 673 | case 'number': 674 | return isFinite(v) ? v : ''; 675 | 676 | default: 677 | return ''; 678 | } 679 | }; 680 | 681 | var encode = function(obj, sep, eq, name) { 682 | sep = sep || '&'; 683 | eq = eq || '='; 684 | if (obj === null) { 685 | obj = undefined; 686 | } 687 | 688 | if (typeof obj === 'object') { 689 | return Object.keys(obj).map(function(k) { 690 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 691 | if (Array.isArray(obj[k])) { 692 | return obj[k].map(function(v) { 693 | return ks + encodeURIComponent(stringifyPrimitive(v)); 694 | }).join(sep); 695 | } else { 696 | return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 697 | } 698 | }).join(sep); 699 | 700 | } 701 | 702 | if (!name) { return ''; } 703 | return encodeURIComponent(stringifyPrimitive(name)) + eq + 704 | encodeURIComponent(stringifyPrimitive(obj)); 705 | }; 706 | 707 | var querystring = createCommonjsModule(function (module, exports) { 708 | 'use strict'; 709 | 710 | exports.decode = exports.parse = decode; 711 | exports.encode = exports.stringify = encode; 712 | }); 713 | 714 | var parse = urlParse; 715 | var resolve = urlResolve; 716 | var resolveObject = urlResolveObject; 717 | var format = urlFormat; 718 | 719 | var Url_1 = Url; 720 | 721 | function Url() { 722 | this.protocol = null; 723 | this.slashes = null; 724 | this.auth = null; 725 | this.host = null; 726 | this.port = null; 727 | this.hostname = null; 728 | this.hash = null; 729 | this.search = null; 730 | this.query = null; 731 | this.pathname = null; 732 | this.path = null; 733 | this.href = null; 734 | } 735 | 736 | // Reference: RFC 3986, RFC 1808, RFC 2396 737 | 738 | // define these here so at least they only have to be 739 | // compiled once on the first module load. 740 | var protocolPattern = /^([a-z0-9.+-]+:)/i; 741 | var portPattern = /:[0-9]*$/; 742 | var simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; 743 | var delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t']; 744 | var unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims); 745 | var autoEscape = ['\''].concat(unwise); 746 | var nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape); 747 | var hostEndingChars = ['/', '?', '#']; 748 | var hostnameMaxLen = 255; 749 | var hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/; 750 | var hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/; 751 | var unsafeProtocol = { 752 | 'javascript': true, 753 | 'javascript:': true 754 | }; 755 | var hostlessProtocol = { 756 | 'javascript': true, 757 | 'javascript:': true 758 | }; 759 | var slashedProtocol = { 760 | 'http': true, 761 | 'https': true, 762 | 'ftp': true, 763 | 'gopher': true, 764 | 'file': true, 765 | 'http:': true, 766 | 'https:': true, 767 | 'ftp:': true, 768 | 'gopher:': true, 769 | 'file:': true 770 | }; 771 | 772 | function urlParse(url, parseQueryString, slashesDenoteHost) { 773 | if (url && util.isObject(url) && url instanceof Url) { return url; } 774 | 775 | var u = new Url; 776 | u.parse(url, parseQueryString, slashesDenoteHost); 777 | return u; 778 | } 779 | 780 | Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { 781 | var this$1 = this; 782 | 783 | if (!util.isString(url)) { 784 | throw new TypeError("Parameter 'url' must be a string, not " + typeof url); 785 | } 786 | 787 | // Copy chrome, IE, opera backslash-handling behavior. 788 | // Back slashes before the query string get converted to forward slashes 789 | // See: https://code.google.com/p/chromium/issues/detail?id=25916 790 | var queryIndex = url.indexOf('?'), 791 | splitter = 792 | (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', 793 | uSplit = url.split(splitter), 794 | slashRegex = /\\/g; 795 | uSplit[0] = uSplit[0].replace(slashRegex, '/'); 796 | url = uSplit.join(splitter); 797 | 798 | var rest = url; 799 | 800 | // trim before proceeding. 801 | // This is to support parse stuff like " http://foo.com \n" 802 | rest = rest.trim(); 803 | 804 | if (!slashesDenoteHost && url.split('#').length === 1) { 805 | // Try fast path regexp 806 | var simplePath = simplePathPattern.exec(rest); 807 | if (simplePath) { 808 | this.path = rest; 809 | this.href = rest; 810 | this.pathname = simplePath[1]; 811 | if (simplePath[2]) { 812 | this.search = simplePath[2]; 813 | if (parseQueryString) { 814 | this.query = querystring.parse(this.search.substr(1)); 815 | } else { 816 | this.query = this.search.substr(1); 817 | } 818 | } else if (parseQueryString) { 819 | this.search = ''; 820 | this.query = {}; 821 | } 822 | return this; 823 | } 824 | } 825 | 826 | var proto = protocolPattern.exec(rest); 827 | if (proto) { 828 | proto = proto[0]; 829 | var lowerProto = proto.toLowerCase(); 830 | this.protocol = lowerProto; 831 | rest = rest.substr(proto.length); 832 | } 833 | 834 | // figure out if it's got a host 835 | // user@server is *always* interpreted as a hostname, and url 836 | // resolution will treat //foo/bar as host=foo,path=bar because that's 837 | // how the browser resolves relative URLs. 838 | if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { 839 | var slashes = rest.substr(0, 2) === '//'; 840 | if (slashes && !(proto && hostlessProtocol[proto])) { 841 | rest = rest.substr(2); 842 | this.slashes = true; 843 | } 844 | } 845 | 846 | if (!hostlessProtocol[proto] && 847 | (slashes || (proto && !slashedProtocol[proto]))) { 848 | 849 | // there's a hostname. 850 | // the first instance of /, ?, ;, or # ends the host. 851 | // 852 | // If there is an @ in the hostname, then non-host chars *are* allowed 853 | // to the left of the last @ sign, unless some host-ending character 854 | // comes *before* the @-sign. 855 | // URLs are obnoxious. 856 | // 857 | // ex: 858 | // http://a@b@c/ => user:a@b host:c 859 | // http://a@b?@c => user:a host:c path:/?@c 860 | 861 | // v0.12 TODO(isaacs): This is not quite how Chrome does things. 862 | // Review our test case against browsers more comprehensively. 863 | 864 | // find the first instance of any hostEndingChars 865 | var hostEnd = -1; 866 | for (var i = 0; i < hostEndingChars.length; i++) { 867 | var hec = rest.indexOf(hostEndingChars[i]); 868 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 869 | { hostEnd = hec; } 870 | } 871 | 872 | // at this point, either we have an explicit point where the 873 | // auth portion cannot go past, or the last @ char is the decider. 874 | var auth, atSign; 875 | if (hostEnd === -1) { 876 | // atSign can be anywhere. 877 | atSign = rest.lastIndexOf('@'); 878 | } else { 879 | // atSign must be in auth portion. 880 | // http://a@b/c@d => host:b auth:a path:/c@d 881 | atSign = rest.lastIndexOf('@', hostEnd); 882 | } 883 | 884 | // Now we have a portion which is definitely the auth. 885 | // Pull that off. 886 | if (atSign !== -1) { 887 | auth = rest.slice(0, atSign); 888 | rest = rest.slice(atSign + 1); 889 | this.auth = decodeURIComponent(auth); 890 | } 891 | 892 | // the host is the remaining to the left of the first non-host char 893 | hostEnd = -1; 894 | for (var i = 0; i < nonHostChars.length; i++) { 895 | var hec = rest.indexOf(nonHostChars[i]); 896 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 897 | { hostEnd = hec; } 898 | } 899 | // if we still have not hit it, then the entire thing is a host. 900 | if (hostEnd === -1) 901 | { hostEnd = rest.length; } 902 | 903 | this.host = rest.slice(0, hostEnd); 904 | rest = rest.slice(hostEnd); 905 | 906 | // pull out port. 907 | this.parseHost(); 908 | 909 | // we've indicated that there is a hostname, 910 | // so even if it's empty, it has to be present. 911 | this.hostname = this.hostname || ''; 912 | 913 | // if hostname begins with [ and ends with ] 914 | // assume that it's an IPv6 address. 915 | var ipv6Hostname = this.hostname[0] === '[' && 916 | this.hostname[this.hostname.length - 1] === ']'; 917 | 918 | // validate a little. 919 | if (!ipv6Hostname) { 920 | var hostparts = this.hostname.split(/\./); 921 | for (var i = 0, l = hostparts.length; i < l; i++) { 922 | var part = hostparts[i]; 923 | if (!part) { continue; } 924 | if (!part.match(hostnamePartPattern)) { 925 | var newpart = ''; 926 | for (var j = 0, k = part.length; j < k; j++) { 927 | if (part.charCodeAt(j) > 127) { 928 | // we replace non-ASCII char with a temporary placeholder 929 | // we need this to make sure size of hostname is not 930 | // broken by replacing non-ASCII by nothing 931 | newpart += 'x'; 932 | } else { 933 | newpart += part[j]; 934 | } 935 | } 936 | // we test again with ASCII char only 937 | if (!newpart.match(hostnamePartPattern)) { 938 | var validParts = hostparts.slice(0, i); 939 | var notHost = hostparts.slice(i + 1); 940 | var bit = part.match(hostnamePartStart); 941 | if (bit) { 942 | validParts.push(bit[1]); 943 | notHost.unshift(bit[2]); 944 | } 945 | if (notHost.length) { 946 | rest = '/' + notHost.join('.') + rest; 947 | } 948 | this$1.hostname = validParts.join('.'); 949 | break; 950 | } 951 | } 952 | } 953 | } 954 | 955 | if (this.hostname.length > hostnameMaxLen) { 956 | this.hostname = ''; 957 | } else { 958 | // hostnames are always lower case. 959 | this.hostname = this.hostname.toLowerCase(); 960 | } 961 | 962 | if (!ipv6Hostname) { 963 | // IDNA Support: Returns a punycoded representation of "domain". 964 | // It only converts parts of the domain name that 965 | // have non-ASCII characters, i.e. it doesn't matter if 966 | // you call it with a domain that already is ASCII-only. 967 | this.hostname = punycode.toASCII(this.hostname); 968 | } 969 | 970 | var p = this.port ? ':' + this.port : ''; 971 | var h = this.hostname || ''; 972 | this.host = h + p; 973 | this.href += this.host; 974 | 975 | // strip [ and ] from the hostname 976 | // the host field still retains them, though 977 | if (ipv6Hostname) { 978 | this.hostname = this.hostname.substr(1, this.hostname.length - 2); 979 | if (rest[0] !== '/') { 980 | rest = '/' + rest; 981 | } 982 | } 983 | } 984 | 985 | // now rest is set to the post-host stuff. 986 | // chop off any delim chars. 987 | if (!unsafeProtocol[lowerProto]) { 988 | 989 | // First, make 100% sure that any "autoEscape" chars get 990 | // escaped, even if encodeURIComponent doesn't think they 991 | // need to be. 992 | for (var i = 0, l = autoEscape.length; i < l; i++) { 993 | var ae = autoEscape[i]; 994 | if (rest.indexOf(ae) === -1) 995 | { continue; } 996 | var esc = encodeURIComponent(ae); 997 | if (esc === ae) { 998 | esc = escape(ae); 999 | } 1000 | rest = rest.split(ae).join(esc); 1001 | } 1002 | } 1003 | 1004 | 1005 | // chop off from the tail first. 1006 | var hash = rest.indexOf('#'); 1007 | if (hash !== -1) { 1008 | // got a fragment string. 1009 | this.hash = rest.substr(hash); 1010 | rest = rest.slice(0, hash); 1011 | } 1012 | var qm = rest.indexOf('?'); 1013 | if (qm !== -1) { 1014 | this.search = rest.substr(qm); 1015 | this.query = rest.substr(qm + 1); 1016 | if (parseQueryString) { 1017 | this.query = querystring.parse(this.query); 1018 | } 1019 | rest = rest.slice(0, qm); 1020 | } else if (parseQueryString) { 1021 | // no query string, but parseQueryString still requested 1022 | this.search = ''; 1023 | this.query = {}; 1024 | } 1025 | if (rest) { this.pathname = rest; } 1026 | if (slashedProtocol[lowerProto] && 1027 | this.hostname && !this.pathname) { 1028 | this.pathname = '/'; 1029 | } 1030 | 1031 | //to support http.request 1032 | if (this.pathname || this.search) { 1033 | var p = this.pathname || ''; 1034 | var s = this.search || ''; 1035 | this.path = p + s; 1036 | } 1037 | 1038 | // finally, reconstruct the href based on what has been validated. 1039 | this.href = this.format(); 1040 | return this; 1041 | }; 1042 | 1043 | // format a parsed object into a url string 1044 | function urlFormat(obj) { 1045 | // ensure it's an object, and not a string url. 1046 | // If it's an obj, this is a no-op. 1047 | // this way, you can call url_format() on strings 1048 | // to clean up potentially wonky urls. 1049 | if (util.isString(obj)) { obj = urlParse(obj); } 1050 | if (!(obj instanceof Url)) { return Url.prototype.format.call(obj); } 1051 | return obj.format(); 1052 | } 1053 | 1054 | Url.prototype.format = function() { 1055 | var auth = this.auth || ''; 1056 | if (auth) { 1057 | auth = encodeURIComponent(auth); 1058 | auth = auth.replace(/%3A/i, ':'); 1059 | auth += '@'; 1060 | } 1061 | 1062 | var protocol = this.protocol || '', 1063 | pathname = this.pathname || '', 1064 | hash = this.hash || '', 1065 | host = false, 1066 | query = ''; 1067 | 1068 | if (this.host) { 1069 | host = auth + this.host; 1070 | } else if (this.hostname) { 1071 | host = auth + (this.hostname.indexOf(':') === -1 ? 1072 | this.hostname : 1073 | '[' + this.hostname + ']'); 1074 | if (this.port) { 1075 | host += ':' + this.port; 1076 | } 1077 | } 1078 | 1079 | if (this.query && 1080 | util.isObject(this.query) && 1081 | Object.keys(this.query).length) { 1082 | query = querystring.stringify(this.query); 1083 | } 1084 | 1085 | var search = this.search || (query && ('?' + query)) || ''; 1086 | 1087 | if (protocol && protocol.substr(-1) !== ':') { protocol += ':'; } 1088 | 1089 | // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. 1090 | // unless they had them to begin with. 1091 | if (this.slashes || 1092 | (!protocol || slashedProtocol[protocol]) && host !== false) { 1093 | host = '//' + (host || ''); 1094 | if (pathname && pathname.charAt(0) !== '/') { pathname = '/' + pathname; } 1095 | } else if (!host) { 1096 | host = ''; 1097 | } 1098 | 1099 | if (hash && hash.charAt(0) !== '#') { hash = '#' + hash; } 1100 | if (search && search.charAt(0) !== '?') { search = '?' + search; } 1101 | 1102 | pathname = pathname.replace(/[?#]/g, function(match) { 1103 | return encodeURIComponent(match); 1104 | }); 1105 | search = search.replace('#', '%23'); 1106 | 1107 | return protocol + host + pathname + search + hash; 1108 | }; 1109 | 1110 | function urlResolve(source, relative) { 1111 | return urlParse(source, false, true).resolve(relative); 1112 | } 1113 | 1114 | Url.prototype.resolve = function(relative) { 1115 | return this.resolveObject(urlParse(relative, false, true)).format(); 1116 | }; 1117 | 1118 | function urlResolveObject(source, relative) { 1119 | if (!source) { return relative; } 1120 | return urlParse(source, false, true).resolveObject(relative); 1121 | } 1122 | 1123 | Url.prototype.resolveObject = function(relative) { 1124 | var this$1 = this; 1125 | 1126 | if (util.isString(relative)) { 1127 | var rel = new Url(); 1128 | rel.parse(relative, false, true); 1129 | relative = rel; 1130 | } 1131 | 1132 | var result = new Url(); 1133 | var tkeys = Object.keys(this); 1134 | for (var tk = 0; tk < tkeys.length; tk++) { 1135 | var tkey = tkeys[tk]; 1136 | result[tkey] = this$1[tkey]; 1137 | } 1138 | 1139 | // hash is always overridden, no matter what. 1140 | // even href="" will remove it. 1141 | result.hash = relative.hash; 1142 | 1143 | // if the relative url is empty, then there's nothing left to do here. 1144 | if (relative.href === '') { 1145 | result.href = result.format(); 1146 | return result; 1147 | } 1148 | 1149 | // hrefs like //foo/bar always cut to the protocol. 1150 | if (relative.slashes && !relative.protocol) { 1151 | // take everything except the protocol from relative 1152 | var rkeys = Object.keys(relative); 1153 | for (var rk = 0; rk < rkeys.length; rk++) { 1154 | var rkey = rkeys[rk]; 1155 | if (rkey !== 'protocol') 1156 | { result[rkey] = relative[rkey]; } 1157 | } 1158 | 1159 | //urlParse appends trailing / to urls like http://www.example.com 1160 | if (slashedProtocol[result.protocol] && 1161 | result.hostname && !result.pathname) { 1162 | result.path = result.pathname = '/'; 1163 | } 1164 | 1165 | result.href = result.format(); 1166 | return result; 1167 | } 1168 | 1169 | if (relative.protocol && relative.protocol !== result.protocol) { 1170 | // if it's a known url protocol, then changing 1171 | // the protocol does weird things 1172 | // first, if it's not file:, then we MUST have a host, 1173 | // and if there was a path 1174 | // to begin with, then we MUST have a path. 1175 | // if it is file:, then the host is dropped, 1176 | // because that's known to be hostless. 1177 | // anything else is assumed to be absolute. 1178 | if (!slashedProtocol[relative.protocol]) { 1179 | var keys = Object.keys(relative); 1180 | for (var v = 0; v < keys.length; v++) { 1181 | var k = keys[v]; 1182 | result[k] = relative[k]; 1183 | } 1184 | result.href = result.format(); 1185 | return result; 1186 | } 1187 | 1188 | result.protocol = relative.protocol; 1189 | if (!relative.host && !hostlessProtocol[relative.protocol]) { 1190 | var relPath = (relative.pathname || '').split('/'); 1191 | while (relPath.length && !(relative.host = relPath.shift())){ } 1192 | if (!relative.host) { relative.host = ''; } 1193 | if (!relative.hostname) { relative.hostname = ''; } 1194 | if (relPath[0] !== '') { relPath.unshift(''); } 1195 | if (relPath.length < 2) { relPath.unshift(''); } 1196 | result.pathname = relPath.join('/'); 1197 | } else { 1198 | result.pathname = relative.pathname; 1199 | } 1200 | result.search = relative.search; 1201 | result.query = relative.query; 1202 | result.host = relative.host || ''; 1203 | result.auth = relative.auth; 1204 | result.hostname = relative.hostname || relative.host; 1205 | result.port = relative.port; 1206 | // to support http.request 1207 | if (result.pathname || result.search) { 1208 | var p = result.pathname || ''; 1209 | var s = result.search || ''; 1210 | result.path = p + s; 1211 | } 1212 | result.slashes = result.slashes || relative.slashes; 1213 | result.href = result.format(); 1214 | return result; 1215 | } 1216 | 1217 | var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), 1218 | isRelAbs = ( 1219 | relative.host || 1220 | relative.pathname && relative.pathname.charAt(0) === '/' 1221 | ), 1222 | mustEndAbs = (isRelAbs || isSourceAbs || 1223 | (result.host && relative.pathname)), 1224 | removeAllDots = mustEndAbs, 1225 | srcPath = result.pathname && result.pathname.split('/') || [], 1226 | relPath = relative.pathname && relative.pathname.split('/') || [], 1227 | psychotic = result.protocol && !slashedProtocol[result.protocol]; 1228 | 1229 | // if the url is a non-slashed url, then relative 1230 | // links like ../.. should be able 1231 | // to crawl up to the hostname, as well. This is strange. 1232 | // result.protocol has already been set by now. 1233 | // Later on, put the first path part into the host field. 1234 | if (psychotic) { 1235 | result.hostname = ''; 1236 | result.port = null; 1237 | if (result.host) { 1238 | if (srcPath[0] === '') { srcPath[0] = result.host; } 1239 | else { srcPath.unshift(result.host); } 1240 | } 1241 | result.host = ''; 1242 | if (relative.protocol) { 1243 | relative.hostname = null; 1244 | relative.port = null; 1245 | if (relative.host) { 1246 | if (relPath[0] === '') { relPath[0] = relative.host; } 1247 | else { relPath.unshift(relative.host); } 1248 | } 1249 | relative.host = null; 1250 | } 1251 | mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); 1252 | } 1253 | 1254 | if (isRelAbs) { 1255 | // it's absolute. 1256 | result.host = (relative.host || relative.host === '') ? 1257 | relative.host : result.host; 1258 | result.hostname = (relative.hostname || relative.hostname === '') ? 1259 | relative.hostname : result.hostname; 1260 | result.search = relative.search; 1261 | result.query = relative.query; 1262 | srcPath = relPath; 1263 | // fall through to the dot-handling below. 1264 | } else if (relPath.length) { 1265 | // it's relative 1266 | // throw away the existing file, and take the new path instead. 1267 | if (!srcPath) { srcPath = []; } 1268 | srcPath.pop(); 1269 | srcPath = srcPath.concat(relPath); 1270 | result.search = relative.search; 1271 | result.query = relative.query; 1272 | } else if (!util.isNullOrUndefined(relative.search)) { 1273 | // just pull out the search. 1274 | // like href='?foo'. 1275 | // Put this after the other two cases because it simplifies the booleans 1276 | if (psychotic) { 1277 | result.hostname = result.host = srcPath.shift(); 1278 | //occationaly the auth can get stuck only in host 1279 | //this especially happens in cases like 1280 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1281 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1282 | result.host.split('@') : false; 1283 | if (authInHost) { 1284 | result.auth = authInHost.shift(); 1285 | result.host = result.hostname = authInHost.shift(); 1286 | } 1287 | } 1288 | result.search = relative.search; 1289 | result.query = relative.query; 1290 | //to support http.request 1291 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1292 | result.path = (result.pathname ? result.pathname : '') + 1293 | (result.search ? result.search : ''); 1294 | } 1295 | result.href = result.format(); 1296 | return result; 1297 | } 1298 | 1299 | if (!srcPath.length) { 1300 | // no path at all. easy. 1301 | // we've already handled the other stuff above. 1302 | result.pathname = null; 1303 | //to support http.request 1304 | if (result.search) { 1305 | result.path = '/' + result.search; 1306 | } else { 1307 | result.path = null; 1308 | } 1309 | result.href = result.format(); 1310 | return result; 1311 | } 1312 | 1313 | // if a url ENDs in . or .., then it must get a trailing slash. 1314 | // however, if it ends in anything else non-slashy, 1315 | // then it must NOT get a trailing slash. 1316 | var last = srcPath.slice(-1)[0]; 1317 | var hasTrailingSlash = ( 1318 | (result.host || relative.host || srcPath.length > 1) && 1319 | (last === '.' || last === '..') || last === ''); 1320 | 1321 | // strip single dots, resolve double dots to parent dir 1322 | // if the path tries to go above the root, `up` ends up > 0 1323 | var up = 0; 1324 | for (var i = srcPath.length; i >= 0; i--) { 1325 | last = srcPath[i]; 1326 | if (last === '.') { 1327 | srcPath.splice(i, 1); 1328 | } else if (last === '..') { 1329 | srcPath.splice(i, 1); 1330 | up++; 1331 | } else if (up) { 1332 | srcPath.splice(i, 1); 1333 | up--; 1334 | } 1335 | } 1336 | 1337 | // if the path is allowed to go above the root, restore leading ..s 1338 | if (!mustEndAbs && !removeAllDots) { 1339 | for (; up--; up) { 1340 | srcPath.unshift('..'); 1341 | } 1342 | } 1343 | 1344 | if (mustEndAbs && srcPath[0] !== '' && 1345 | (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { 1346 | srcPath.unshift(''); 1347 | } 1348 | 1349 | if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { 1350 | srcPath.push(''); 1351 | } 1352 | 1353 | var isAbsolute = srcPath[0] === '' || 1354 | (srcPath[0] && srcPath[0].charAt(0) === '/'); 1355 | 1356 | // put the host back 1357 | if (psychotic) { 1358 | result.hostname = result.host = isAbsolute ? '' : 1359 | srcPath.length ? srcPath.shift() : ''; 1360 | //occationaly the auth can get stuck only in host 1361 | //this especially happens in cases like 1362 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1363 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1364 | result.host.split('@') : false; 1365 | if (authInHost) { 1366 | result.auth = authInHost.shift(); 1367 | result.host = result.hostname = authInHost.shift(); 1368 | } 1369 | } 1370 | 1371 | mustEndAbs = mustEndAbs || (result.host && srcPath.length); 1372 | 1373 | if (mustEndAbs && !isAbsolute) { 1374 | srcPath.unshift(''); 1375 | } 1376 | 1377 | if (!srcPath.length) { 1378 | result.pathname = null; 1379 | result.path = null; 1380 | } else { 1381 | result.pathname = srcPath.join('/'); 1382 | } 1383 | 1384 | //to support request.http 1385 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1386 | result.path = (result.pathname ? result.pathname : '') + 1387 | (result.search ? result.search : ''); 1388 | } 1389 | result.auth = relative.auth || result.auth; 1390 | result.slashes = result.slashes || relative.slashes; 1391 | result.href = result.format(); 1392 | return result; 1393 | }; 1394 | 1395 | Url.prototype.parseHost = function() { 1396 | var host = this.host; 1397 | var port = portPattern.exec(host); 1398 | if (port) { 1399 | port = port[0]; 1400 | if (port !== ':') { 1401 | this.port = port.substr(1); 1402 | } 1403 | host = host.substr(0, host.length - port.length); 1404 | } 1405 | if (host) { this.hostname = host; } 1406 | }; 1407 | 1408 | var url = { 1409 | parse: parse, 1410 | resolve: resolve, 1411 | resolveObject: resolveObject, 1412 | format: format, 1413 | Url: Url_1 1414 | }; 1415 | 1416 | function install (Vue, options) { 1417 | if (install.installed) { return } 1418 | install.installed = true; 1419 | 1420 | Object.defineProperty(Vue.prototype, '$wechatAuth', { 1421 | get: function get () { return this.$root._wechatAuth } 1422 | }); 1423 | 1424 | Vue.mixin({ 1425 | beforeCreate: function beforeCreate () { 1426 | if (this.$options.wechatAuth) { 1427 | this._wechatAuth = this.$options.wechatAuth; 1428 | } 1429 | } 1430 | }); 1431 | } 1432 | 1433 | var config = { 1434 | git: 'https://github.com/raychenfj/v-wechat-auth' 1435 | }; 1436 | 1437 | var requiredProps = ['appId', 'scope', 'authorize']; 1438 | var defaultOptions = { 1439 | autoRedirect: true, 1440 | state: '', 1441 | authorize: function authorize () { 1442 | console.error('should implement authorize method in options'); 1443 | } 1444 | }; 1445 | 1446 | function isFunction (fn) { 1447 | return fn && typeof fn === 'function' 1448 | } 1449 | 1450 | var WechatAuth = function WechatAuth (options) { 1451 | var this$1 = this; 1452 | 1453 | this.options = Object.assign(defaultOptions, options); 1454 | 1455 | requiredProps.forEach(function (prop) { 1456 | if (!this$1.options[prop]) { 1457 | console.error(("required property " + prop + " is missing in options, please visit " + (config.git) + " for more info.")); 1458 | } 1459 | }); 1460 | 1461 | this.user = null; 1462 | }; 1463 | 1464 | /** 1465 | * authorize 1466 | * @param {*} onSuccess 1467 | * @param {*} onFail 1468 | * @returns {Promise} 1469 | */ 1470 | WechatAuth.prototype.authorize = function authorize (onSuccess, onFail) { 1471 | var this$1 = this; 1472 | 1473 | var urlObj = url.parse(window.location.href, true); 1474 | 1475 | if (urlObj.query && !urlObj.query.code) { 1476 | // delete state in query in url 1477 | delete urlObj.query.state; 1478 | delete urlObj.search; 1479 | return this.redirect(url.format(urlObj)) 1480 | } 1481 | 1482 | // decorated success 1483 | var success = function (data) { 1484 | var user = this$1.onSuccess(data); 1485 | if (isFunction(onSuccess)) { 1486 | onSuccess(user); 1487 | } 1488 | return user 1489 | }; 1490 | 1491 | // decorated fail 1492 | var fail = function (e) { 1493 | this$1.onFail(e); 1494 | if (isFunction(onFail)) { 1495 | onFail(e); 1496 | } 1497 | }; 1498 | 1499 | try { 1500 | // if options.authorize use callback 1501 | var promise = this.options.authorize(urlObj.query.code, success, fail); 1502 | 1503 | // if options.authorize return promise 1504 | if (promise && promise instanceof Promise) { 1505 | return promise.then(success).catch(fail) 1506 | } 1507 | } catch (e) { 1508 | fail(e); 1509 | } 1510 | }; 1511 | 1512 | /** 1513 | * onSuccess 1514 | * @private 1515 | * @param {*} data 1516 | */ 1517 | WechatAuth.prototype.onSuccess = function onSuccess (data) { 1518 | if (!data.openid && this.options.autoRedirect) { 1519 | var urlObj = url.parse(window.location.href, true); 1520 | // delete code and state in query in url 1521 | delete urlObj.query.code; 1522 | delete urlObj.query.state; 1523 | delete urlObj.search; 1524 | return this.redirect(url.format(urlObj)) 1525 | } 1526 | this.user = data; 1527 | return this.user 1528 | }; 1529 | 1530 | /** 1531 | * onFail 1532 | * @private 1533 | * @param {*} e 1534 | */ 1535 | WechatAuth.prototype.onFail = function onFail (e) { 1536 | console.error('error occurs when authorize from back end'); 1537 | console.error(e); 1538 | }; 1539 | 1540 | /** 1541 | * redirect to wechat auth url 1542 | * @param {*} url 1543 | */ 1544 | WechatAuth.prototype.redirect = function redirect (url$$1) { 1545 | var options = this.options; 1546 | window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + (options.appId) + "&redirect_uri=" + (encodeURIComponent(url$$1)) + "&response_type=code&scope=" + (options.scope) + "&state=" + (options.state) + "#wechat_redirect"; 1547 | }; 1548 | 1549 | WechatAuth.install = install; 1550 | 1551 | export default WechatAuth; 1552 | -------------------------------------------------------------------------------- /dist/v-wechat-auth.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * v-wechat-auth v1.0.0 3 | * (c) 2018 fengjun.chen 4 | * Released under the MIT License. 5 | */ 6 | 7 | (function (global, factory) { 8 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 9 | typeof define === 'function' && define.amd ? define(factory) : 10 | (global.VWechatAuth = factory()); 11 | }(this, (function () { 'use strict'; 12 | 13 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 14 | 15 | 16 | 17 | 18 | 19 | function createCommonjsModule(fn, module) { 20 | return module = { exports: {} }, fn(module, module.exports), module.exports; 21 | } 22 | 23 | var punycode = createCommonjsModule(function (module, exports) { 24 | /*! https://mths.be/punycode v1.3.2 by @mathias */ 25 | (function(root) { 26 | 27 | /** Detect free variables */ 28 | var freeExports = 'object' == 'object' && exports && 29 | !exports.nodeType && exports; 30 | var freeModule = 'object' == 'object' && module && 31 | !module.nodeType && module; 32 | var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal; 33 | if ( 34 | freeGlobal.global === freeGlobal || 35 | freeGlobal.window === freeGlobal || 36 | freeGlobal.self === freeGlobal 37 | ) { 38 | root = freeGlobal; 39 | } 40 | 41 | /** 42 | * The `punycode` object. 43 | * @name punycode 44 | * @type Object 45 | */ 46 | var punycode, 47 | 48 | /** Highest positive signed 32-bit float value */ 49 | maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 50 | 51 | /** Bootstring parameters */ 52 | base = 36, 53 | tMin = 1, 54 | tMax = 26, 55 | skew = 38, 56 | damp = 700, 57 | initialBias = 72, 58 | initialN = 128, // 0x80 59 | delimiter = '-', // '\x2D' 60 | 61 | /** Regular expressions */ 62 | regexPunycode = /^xn--/, 63 | regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars 64 | regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators 65 | 66 | /** Error messages */ 67 | errors = { 68 | 'overflow': 'Overflow: input needs wider integers to process', 69 | 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 70 | 'invalid-input': 'Invalid input' 71 | }, 72 | 73 | /** Convenience shortcuts */ 74 | baseMinusTMin = base - tMin, 75 | floor = Math.floor, 76 | stringFromCharCode = String.fromCharCode, 77 | 78 | /** Temporary variable */ 79 | key; 80 | 81 | /*--------------------------------------------------------------------------*/ 82 | 83 | /** 84 | * A generic error utility function. 85 | * @private 86 | * @param {String} type The error type. 87 | * @returns {Error} Throws a `RangeError` with the applicable error message. 88 | */ 89 | function error(type) { 90 | throw RangeError(errors[type]); 91 | } 92 | 93 | /** 94 | * A generic `Array#map` utility function. 95 | * @private 96 | * @param {Array} array The array to iterate over. 97 | * @param {Function} callback The function that gets called for every array 98 | * item. 99 | * @returns {Array} A new array of values returned by the callback function. 100 | */ 101 | function map(array, fn) { 102 | var length = array.length; 103 | var result = []; 104 | while (length--) { 105 | result[length] = fn(array[length]); 106 | } 107 | return result; 108 | } 109 | 110 | /** 111 | * A simple `Array#map`-like wrapper to work with domain name strings or email 112 | * addresses. 113 | * @private 114 | * @param {String} domain The domain name or email address. 115 | * @param {Function} callback The function that gets called for every 116 | * character. 117 | * @returns {Array} A new string of characters returned by the callback 118 | * function. 119 | */ 120 | function mapDomain(string, fn) { 121 | var parts = string.split('@'); 122 | var result = ''; 123 | if (parts.length > 1) { 124 | // In email addresses, only the domain name should be punycoded. Leave 125 | // the local part (i.e. everything up to `@`) intact. 126 | result = parts[0] + '@'; 127 | string = parts[1]; 128 | } 129 | // Avoid `split(regex)` for IE8 compatibility. See #17. 130 | string = string.replace(regexSeparators, '\x2E'); 131 | var labels = string.split('.'); 132 | var encoded = map(labels, fn).join('.'); 133 | return result + encoded; 134 | } 135 | 136 | /** 137 | * Creates an array containing the numeric code points of each Unicode 138 | * character in the string. While JavaScript uses UCS-2 internally, 139 | * this function will convert a pair of surrogate halves (each of which 140 | * UCS-2 exposes as separate characters) into a single code point, 141 | * matching UTF-16. 142 | * @see `punycode.ucs2.encode` 143 | * @see 144 | * @memberOf punycode.ucs2 145 | * @name decode 146 | * @param {String} string The Unicode input string (UCS-2). 147 | * @returns {Array} The new array of code points. 148 | */ 149 | function ucs2decode(string) { 150 | var output = [], 151 | counter = 0, 152 | length = string.length, 153 | value, 154 | extra; 155 | while (counter < length) { 156 | value = string.charCodeAt(counter++); 157 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 158 | // high surrogate, and there is a next character 159 | extra = string.charCodeAt(counter++); 160 | if ((extra & 0xFC00) == 0xDC00) { // low surrogate 161 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 162 | } else { 163 | // unmatched surrogate; only append this code unit, in case the next 164 | // code unit is the high surrogate of a surrogate pair 165 | output.push(value); 166 | counter--; 167 | } 168 | } else { 169 | output.push(value); 170 | } 171 | } 172 | return output; 173 | } 174 | 175 | /** 176 | * Creates a string based on an array of numeric code points. 177 | * @see `punycode.ucs2.decode` 178 | * @memberOf punycode.ucs2 179 | * @name encode 180 | * @param {Array} codePoints The array of numeric code points. 181 | * @returns {String} The new Unicode string (UCS-2). 182 | */ 183 | function ucs2encode(array) { 184 | return map(array, function(value) { 185 | var output = ''; 186 | if (value > 0xFFFF) { 187 | value -= 0x10000; 188 | output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); 189 | value = 0xDC00 | value & 0x3FF; 190 | } 191 | output += stringFromCharCode(value); 192 | return output; 193 | }).join(''); 194 | } 195 | 196 | /** 197 | * Converts a basic code point into a digit/integer. 198 | * @see `digitToBasic()` 199 | * @private 200 | * @param {Number} codePoint The basic numeric code point value. 201 | * @returns {Number} The numeric value of a basic code point (for use in 202 | * representing integers) in the range `0` to `base - 1`, or `base` if 203 | * the code point does not represent a value. 204 | */ 205 | function basicToDigit(codePoint) { 206 | if (codePoint - 48 < 10) { 207 | return codePoint - 22; 208 | } 209 | if (codePoint - 65 < 26) { 210 | return codePoint - 65; 211 | } 212 | if (codePoint - 97 < 26) { 213 | return codePoint - 97; 214 | } 215 | return base; 216 | } 217 | 218 | /** 219 | * Converts a digit/integer into a basic code point. 220 | * @see `basicToDigit()` 221 | * @private 222 | * @param {Number} digit The numeric value of a basic code point. 223 | * @returns {Number} The basic code point whose value (when used for 224 | * representing integers) is `digit`, which needs to be in the range 225 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 226 | * used; else, the lowercase form is used. The behavior is undefined 227 | * if `flag` is non-zero and `digit` has no uppercase form. 228 | */ 229 | function digitToBasic(digit, flag) { 230 | // 0..25 map to ASCII a..z or A..Z 231 | // 26..35 map to ASCII 0..9 232 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 233 | } 234 | 235 | /** 236 | * Bias adaptation function as per section 3.4 of RFC 3492. 237 | * http://tools.ietf.org/html/rfc3492#section-3.4 238 | * @private 239 | */ 240 | function adapt(delta, numPoints, firstTime) { 241 | var k = 0; 242 | delta = firstTime ? floor(delta / damp) : delta >> 1; 243 | delta += floor(delta / numPoints); 244 | for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 245 | delta = floor(delta / baseMinusTMin); 246 | } 247 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 248 | } 249 | 250 | /** 251 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode 252 | * symbols. 253 | * @memberOf punycode 254 | * @param {String} input The Punycode string of ASCII-only symbols. 255 | * @returns {String} The resulting string of Unicode symbols. 256 | */ 257 | function decode(input) { 258 | // Don't use UCS-2 259 | var output = [], 260 | inputLength = input.length, 261 | out, 262 | i = 0, 263 | n = initialN, 264 | bias = initialBias, 265 | basic, 266 | j, 267 | index, 268 | oldi, 269 | w, 270 | k, 271 | digit, 272 | t, 273 | /** Cached calculation results */ 274 | baseMinusT; 275 | 276 | // Handle the basic code points: let `basic` be the number of input code 277 | // points before the last delimiter, or `0` if there is none, then copy 278 | // the first basic code points to the output. 279 | 280 | basic = input.lastIndexOf(delimiter); 281 | if (basic < 0) { 282 | basic = 0; 283 | } 284 | 285 | for (j = 0; j < basic; ++j) { 286 | // if it's not a basic code point 287 | if (input.charCodeAt(j) >= 0x80) { 288 | error('not-basic'); 289 | } 290 | output.push(input.charCodeAt(j)); 291 | } 292 | 293 | // Main decoding loop: start just after the last delimiter if any basic code 294 | // points were copied; start at the beginning otherwise. 295 | 296 | for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 297 | 298 | // `index` is the index of the next character to be consumed. 299 | // Decode a generalized variable-length integer into `delta`, 300 | // which gets added to `i`. The overflow checking is easier 301 | // if we increase `i` as we go, then subtract off its starting 302 | // value at the end to obtain `delta`. 303 | for (oldi = i, w = 1, k = base; /* no condition */; k += base) { 304 | 305 | if (index >= inputLength) { 306 | error('invalid-input'); 307 | } 308 | 309 | digit = basicToDigit(input.charCodeAt(index++)); 310 | 311 | if (digit >= base || digit > floor((maxInt - i) / w)) { 312 | error('overflow'); 313 | } 314 | 315 | i += digit * w; 316 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 317 | 318 | if (digit < t) { 319 | break; 320 | } 321 | 322 | baseMinusT = base - t; 323 | if (w > floor(maxInt / baseMinusT)) { 324 | error('overflow'); 325 | } 326 | 327 | w *= baseMinusT; 328 | 329 | } 330 | 331 | out = output.length + 1; 332 | bias = adapt(i - oldi, out, oldi == 0); 333 | 334 | // `i` was supposed to wrap around from `out` to `0`, 335 | // incrementing `n` each time, so we'll fix that now: 336 | if (floor(i / out) > maxInt - n) { 337 | error('overflow'); 338 | } 339 | 340 | n += floor(i / out); 341 | i %= out; 342 | 343 | // Insert `n` at position `i` of the output 344 | output.splice(i++, 0, n); 345 | 346 | } 347 | 348 | return ucs2encode(output); 349 | } 350 | 351 | /** 352 | * Converts a string of Unicode symbols (e.g. a domain name label) to a 353 | * Punycode string of ASCII-only symbols. 354 | * @memberOf punycode 355 | * @param {String} input The string of Unicode symbols. 356 | * @returns {String} The resulting Punycode string of ASCII-only symbols. 357 | */ 358 | function encode(input) { 359 | var n, 360 | delta, 361 | handledCPCount, 362 | basicLength, 363 | bias, 364 | j, 365 | m, 366 | q, 367 | k, 368 | t, 369 | currentValue, 370 | output = [], 371 | /** `inputLength` will hold the number of code points in `input`. */ 372 | inputLength, 373 | /** Cached calculation results */ 374 | handledCPCountPlusOne, 375 | baseMinusT, 376 | qMinusT; 377 | 378 | // Convert the input in UCS-2 to Unicode 379 | input = ucs2decode(input); 380 | 381 | // Cache the length 382 | inputLength = input.length; 383 | 384 | // Initialize the state 385 | n = initialN; 386 | delta = 0; 387 | bias = initialBias; 388 | 389 | // Handle the basic code points 390 | for (j = 0; j < inputLength; ++j) { 391 | currentValue = input[j]; 392 | if (currentValue < 0x80) { 393 | output.push(stringFromCharCode(currentValue)); 394 | } 395 | } 396 | 397 | handledCPCount = basicLength = output.length; 398 | 399 | // `handledCPCount` is the number of code points that have been handled; 400 | // `basicLength` is the number of basic code points. 401 | 402 | // Finish the basic string - if it is not empty - with a delimiter 403 | if (basicLength) { 404 | output.push(delimiter); 405 | } 406 | 407 | // Main encoding loop: 408 | while (handledCPCount < inputLength) { 409 | 410 | // All non-basic code points < n have been handled already. Find the next 411 | // larger one: 412 | for (m = maxInt, j = 0; j < inputLength; ++j) { 413 | currentValue = input[j]; 414 | if (currentValue >= n && currentValue < m) { 415 | m = currentValue; 416 | } 417 | } 418 | 419 | // Increase `delta` enough to advance the decoder's state to , 420 | // but guard against overflow 421 | handledCPCountPlusOne = handledCPCount + 1; 422 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 423 | error('overflow'); 424 | } 425 | 426 | delta += (m - n) * handledCPCountPlusOne; 427 | n = m; 428 | 429 | for (j = 0; j < inputLength; ++j) { 430 | currentValue = input[j]; 431 | 432 | if (currentValue < n && ++delta > maxInt) { 433 | error('overflow'); 434 | } 435 | 436 | if (currentValue == n) { 437 | // Represent delta as a generalized variable-length integer 438 | for (q = delta, k = base; /* no condition */; k += base) { 439 | t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 440 | if (q < t) { 441 | break; 442 | } 443 | qMinusT = q - t; 444 | baseMinusT = base - t; 445 | output.push( 446 | stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 447 | ); 448 | q = floor(qMinusT / baseMinusT); 449 | } 450 | 451 | output.push(stringFromCharCode(digitToBasic(q, 0))); 452 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 453 | delta = 0; 454 | ++handledCPCount; 455 | } 456 | } 457 | 458 | ++delta; 459 | ++n; 460 | 461 | } 462 | return output.join(''); 463 | } 464 | 465 | /** 466 | * Converts a Punycode string representing a domain name or an email address 467 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e. 468 | * it doesn't matter if you call it on a string that has already been 469 | * converted to Unicode. 470 | * @memberOf punycode 471 | * @param {String} input The Punycoded domain name or email address to 472 | * convert to Unicode. 473 | * @returns {String} The Unicode representation of the given Punycode 474 | * string. 475 | */ 476 | function toUnicode(input) { 477 | return mapDomain(input, function(string) { 478 | return regexPunycode.test(string) 479 | ? decode(string.slice(4).toLowerCase()) 480 | : string; 481 | }); 482 | } 483 | 484 | /** 485 | * Converts a Unicode string representing a domain name or an email address to 486 | * Punycode. Only the non-ASCII parts of the domain name will be converted, 487 | * i.e. it doesn't matter if you call it with a domain that's already in 488 | * ASCII. 489 | * @memberOf punycode 490 | * @param {String} input The domain name or email address to convert, as a 491 | * Unicode string. 492 | * @returns {String} The Punycode representation of the given domain name or 493 | * email address. 494 | */ 495 | function toASCII(input) { 496 | return mapDomain(input, function(string) { 497 | return regexNonASCII.test(string) 498 | ? 'xn--' + encode(string) 499 | : string; 500 | }); 501 | } 502 | 503 | /*--------------------------------------------------------------------------*/ 504 | 505 | /** Define the public API */ 506 | punycode = { 507 | /** 508 | * A string representing the current Punycode.js version number. 509 | * @memberOf punycode 510 | * @type String 511 | */ 512 | 'version': '1.3.2', 513 | /** 514 | * An object of methods to convert from JavaScript's internal character 515 | * representation (UCS-2) to Unicode code points, and back. 516 | * @see 517 | * @memberOf punycode 518 | * @type Object 519 | */ 520 | 'ucs2': { 521 | 'decode': ucs2decode, 522 | 'encode': ucs2encode 523 | }, 524 | 'decode': decode, 525 | 'encode': encode, 526 | 'toASCII': toASCII, 527 | 'toUnicode': toUnicode 528 | }; 529 | 530 | /** Expose `punycode` */ 531 | // Some AMD build optimizers, like r.js, check for specific condition patterns 532 | // like the following: 533 | if ( 534 | typeof undefined == 'function' && 535 | typeof undefined.amd == 'object' && 536 | undefined.amd 537 | ) { 538 | undefined('punycode', function() { 539 | return punycode; 540 | }); 541 | } else if (freeExports && freeModule) { 542 | if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ 543 | freeModule.exports = punycode; 544 | } else { // in Narwhal or RingoJS v0.7.0- 545 | for (key in punycode) { 546 | punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); 547 | } 548 | } 549 | } else { // in Rhino or a web browser 550 | root.punycode = punycode; 551 | } 552 | 553 | }(commonjsGlobal)); 554 | }); 555 | 556 | var util = { 557 | isString: function(arg) { 558 | return typeof(arg) === 'string'; 559 | }, 560 | isObject: function(arg) { 561 | return typeof(arg) === 'object' && arg !== null; 562 | }, 563 | isNull: function(arg) { 564 | return arg === null; 565 | }, 566 | isNullOrUndefined: function(arg) { 567 | return arg == null; 568 | } 569 | }; 570 | 571 | // Copyright Joyent, Inc. and other Node contributors. 572 | // 573 | // Permission is hereby granted, free of charge, to any person obtaining a 574 | // copy of this software and associated documentation files (the 575 | // "Software"), to deal in the Software without restriction, including 576 | // without limitation the rights to use, copy, modify, merge, publish, 577 | // distribute, sublicense, and/or sell copies of the Software, and to permit 578 | // persons to whom the Software is furnished to do so, subject to the 579 | // following conditions: 580 | // 581 | // The above copyright notice and this permission notice shall be included 582 | // in all copies or substantial portions of the Software. 583 | // 584 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 585 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 586 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 587 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 588 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 589 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 590 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 591 | 592 | // If obj.hasOwnProperty has been overridden, then calling 593 | // obj.hasOwnProperty(prop) will break. 594 | // See: https://github.com/joyent/node/issues/1707 595 | function hasOwnProperty(obj, prop) { 596 | return Object.prototype.hasOwnProperty.call(obj, prop); 597 | } 598 | 599 | var decode = function(qs, sep, eq, options) { 600 | sep = sep || '&'; 601 | eq = eq || '='; 602 | var obj = {}; 603 | 604 | if (typeof qs !== 'string' || qs.length === 0) { 605 | return obj; 606 | } 607 | 608 | var regexp = /\+/g; 609 | qs = qs.split(sep); 610 | 611 | var maxKeys = 1000; 612 | if (options && typeof options.maxKeys === 'number') { 613 | maxKeys = options.maxKeys; 614 | } 615 | 616 | var len = qs.length; 617 | // maxKeys <= 0 means that we should not limit keys count 618 | if (maxKeys > 0 && len > maxKeys) { 619 | len = maxKeys; 620 | } 621 | 622 | for (var i = 0; i < len; ++i) { 623 | var x = qs[i].replace(regexp, '%20'), 624 | idx = x.indexOf(eq), 625 | kstr, vstr, k, v; 626 | 627 | if (idx >= 0) { 628 | kstr = x.substr(0, idx); 629 | vstr = x.substr(idx + 1); 630 | } else { 631 | kstr = x; 632 | vstr = ''; 633 | } 634 | 635 | k = decodeURIComponent(kstr); 636 | v = decodeURIComponent(vstr); 637 | 638 | if (!hasOwnProperty(obj, k)) { 639 | obj[k] = v; 640 | } else if (Array.isArray(obj[k])) { 641 | obj[k].push(v); 642 | } else { 643 | obj[k] = [obj[k], v]; 644 | } 645 | } 646 | 647 | return obj; 648 | }; 649 | 650 | // Copyright Joyent, Inc. and other Node contributors. 651 | // 652 | // Permission is hereby granted, free of charge, to any person obtaining a 653 | // copy of this software and associated documentation files (the 654 | // "Software"), to deal in the Software without restriction, including 655 | // without limitation the rights to use, copy, modify, merge, publish, 656 | // distribute, sublicense, and/or sell copies of the Software, and to permit 657 | // persons to whom the Software is furnished to do so, subject to the 658 | // following conditions: 659 | // 660 | // The above copyright notice and this permission notice shall be included 661 | // in all copies or substantial portions of the Software. 662 | // 663 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 664 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 665 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 666 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 667 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 668 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 669 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 670 | 671 | var stringifyPrimitive = function(v) { 672 | switch (typeof v) { 673 | case 'string': 674 | return v; 675 | 676 | case 'boolean': 677 | return v ? 'true' : 'false'; 678 | 679 | case 'number': 680 | return isFinite(v) ? v : ''; 681 | 682 | default: 683 | return ''; 684 | } 685 | }; 686 | 687 | var encode = function(obj, sep, eq, name) { 688 | sep = sep || '&'; 689 | eq = eq || '='; 690 | if (obj === null) { 691 | obj = undefined; 692 | } 693 | 694 | if (typeof obj === 'object') { 695 | return Object.keys(obj).map(function(k) { 696 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 697 | if (Array.isArray(obj[k])) { 698 | return obj[k].map(function(v) { 699 | return ks + encodeURIComponent(stringifyPrimitive(v)); 700 | }).join(sep); 701 | } else { 702 | return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 703 | } 704 | }).join(sep); 705 | 706 | } 707 | 708 | if (!name) { return ''; } 709 | return encodeURIComponent(stringifyPrimitive(name)) + eq + 710 | encodeURIComponent(stringifyPrimitive(obj)); 711 | }; 712 | 713 | var querystring = createCommonjsModule(function (module, exports) { 714 | 'use strict'; 715 | 716 | exports.decode = exports.parse = decode; 717 | exports.encode = exports.stringify = encode; 718 | }); 719 | 720 | var parse = urlParse; 721 | var resolve = urlResolve; 722 | var resolveObject = urlResolveObject; 723 | var format = urlFormat; 724 | 725 | var Url_1 = Url; 726 | 727 | function Url() { 728 | this.protocol = null; 729 | this.slashes = null; 730 | this.auth = null; 731 | this.host = null; 732 | this.port = null; 733 | this.hostname = null; 734 | this.hash = null; 735 | this.search = null; 736 | this.query = null; 737 | this.pathname = null; 738 | this.path = null; 739 | this.href = null; 740 | } 741 | 742 | // Reference: RFC 3986, RFC 1808, RFC 2396 743 | 744 | // define these here so at least they only have to be 745 | // compiled once on the first module load. 746 | var protocolPattern = /^([a-z0-9.+-]+:)/i; 747 | var portPattern = /:[0-9]*$/; 748 | var simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; 749 | var delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t']; 750 | var unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims); 751 | var autoEscape = ['\''].concat(unwise); 752 | var nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape); 753 | var hostEndingChars = ['/', '?', '#']; 754 | var hostnameMaxLen = 255; 755 | var hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/; 756 | var hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/; 757 | var unsafeProtocol = { 758 | 'javascript': true, 759 | 'javascript:': true 760 | }; 761 | var hostlessProtocol = { 762 | 'javascript': true, 763 | 'javascript:': true 764 | }; 765 | var slashedProtocol = { 766 | 'http': true, 767 | 'https': true, 768 | 'ftp': true, 769 | 'gopher': true, 770 | 'file': true, 771 | 'http:': true, 772 | 'https:': true, 773 | 'ftp:': true, 774 | 'gopher:': true, 775 | 'file:': true 776 | }; 777 | 778 | function urlParse(url, parseQueryString, slashesDenoteHost) { 779 | if (url && util.isObject(url) && url instanceof Url) { return url; } 780 | 781 | var u = new Url; 782 | u.parse(url, parseQueryString, slashesDenoteHost); 783 | return u; 784 | } 785 | 786 | Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { 787 | var this$1 = this; 788 | 789 | if (!util.isString(url)) { 790 | throw new TypeError("Parameter 'url' must be a string, not " + typeof url); 791 | } 792 | 793 | // Copy chrome, IE, opera backslash-handling behavior. 794 | // Back slashes before the query string get converted to forward slashes 795 | // See: https://code.google.com/p/chromium/issues/detail?id=25916 796 | var queryIndex = url.indexOf('?'), 797 | splitter = 798 | (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', 799 | uSplit = url.split(splitter), 800 | slashRegex = /\\/g; 801 | uSplit[0] = uSplit[0].replace(slashRegex, '/'); 802 | url = uSplit.join(splitter); 803 | 804 | var rest = url; 805 | 806 | // trim before proceeding. 807 | // This is to support parse stuff like " http://foo.com \n" 808 | rest = rest.trim(); 809 | 810 | if (!slashesDenoteHost && url.split('#').length === 1) { 811 | // Try fast path regexp 812 | var simplePath = simplePathPattern.exec(rest); 813 | if (simplePath) { 814 | this.path = rest; 815 | this.href = rest; 816 | this.pathname = simplePath[1]; 817 | if (simplePath[2]) { 818 | this.search = simplePath[2]; 819 | if (parseQueryString) { 820 | this.query = querystring.parse(this.search.substr(1)); 821 | } else { 822 | this.query = this.search.substr(1); 823 | } 824 | } else if (parseQueryString) { 825 | this.search = ''; 826 | this.query = {}; 827 | } 828 | return this; 829 | } 830 | } 831 | 832 | var proto = protocolPattern.exec(rest); 833 | if (proto) { 834 | proto = proto[0]; 835 | var lowerProto = proto.toLowerCase(); 836 | this.protocol = lowerProto; 837 | rest = rest.substr(proto.length); 838 | } 839 | 840 | // figure out if it's got a host 841 | // user@server is *always* interpreted as a hostname, and url 842 | // resolution will treat //foo/bar as host=foo,path=bar because that's 843 | // how the browser resolves relative URLs. 844 | if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { 845 | var slashes = rest.substr(0, 2) === '//'; 846 | if (slashes && !(proto && hostlessProtocol[proto])) { 847 | rest = rest.substr(2); 848 | this.slashes = true; 849 | } 850 | } 851 | 852 | if (!hostlessProtocol[proto] && 853 | (slashes || (proto && !slashedProtocol[proto]))) { 854 | 855 | // there's a hostname. 856 | // the first instance of /, ?, ;, or # ends the host. 857 | // 858 | // If there is an @ in the hostname, then non-host chars *are* allowed 859 | // to the left of the last @ sign, unless some host-ending character 860 | // comes *before* the @-sign. 861 | // URLs are obnoxious. 862 | // 863 | // ex: 864 | // http://a@b@c/ => user:a@b host:c 865 | // http://a@b?@c => user:a host:c path:/?@c 866 | 867 | // v0.12 TODO(isaacs): This is not quite how Chrome does things. 868 | // Review our test case against browsers more comprehensively. 869 | 870 | // find the first instance of any hostEndingChars 871 | var hostEnd = -1; 872 | for (var i = 0; i < hostEndingChars.length; i++) { 873 | var hec = rest.indexOf(hostEndingChars[i]); 874 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 875 | { hostEnd = hec; } 876 | } 877 | 878 | // at this point, either we have an explicit point where the 879 | // auth portion cannot go past, or the last @ char is the decider. 880 | var auth, atSign; 881 | if (hostEnd === -1) { 882 | // atSign can be anywhere. 883 | atSign = rest.lastIndexOf('@'); 884 | } else { 885 | // atSign must be in auth portion. 886 | // http://a@b/c@d => host:b auth:a path:/c@d 887 | atSign = rest.lastIndexOf('@', hostEnd); 888 | } 889 | 890 | // Now we have a portion which is definitely the auth. 891 | // Pull that off. 892 | if (atSign !== -1) { 893 | auth = rest.slice(0, atSign); 894 | rest = rest.slice(atSign + 1); 895 | this.auth = decodeURIComponent(auth); 896 | } 897 | 898 | // the host is the remaining to the left of the first non-host char 899 | hostEnd = -1; 900 | for (var i = 0; i < nonHostChars.length; i++) { 901 | var hec = rest.indexOf(nonHostChars[i]); 902 | if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) 903 | { hostEnd = hec; } 904 | } 905 | // if we still have not hit it, then the entire thing is a host. 906 | if (hostEnd === -1) 907 | { hostEnd = rest.length; } 908 | 909 | this.host = rest.slice(0, hostEnd); 910 | rest = rest.slice(hostEnd); 911 | 912 | // pull out port. 913 | this.parseHost(); 914 | 915 | // we've indicated that there is a hostname, 916 | // so even if it's empty, it has to be present. 917 | this.hostname = this.hostname || ''; 918 | 919 | // if hostname begins with [ and ends with ] 920 | // assume that it's an IPv6 address. 921 | var ipv6Hostname = this.hostname[0] === '[' && 922 | this.hostname[this.hostname.length - 1] === ']'; 923 | 924 | // validate a little. 925 | if (!ipv6Hostname) { 926 | var hostparts = this.hostname.split(/\./); 927 | for (var i = 0, l = hostparts.length; i < l; i++) { 928 | var part = hostparts[i]; 929 | if (!part) { continue; } 930 | if (!part.match(hostnamePartPattern)) { 931 | var newpart = ''; 932 | for (var j = 0, k = part.length; j < k; j++) { 933 | if (part.charCodeAt(j) > 127) { 934 | // we replace non-ASCII char with a temporary placeholder 935 | // we need this to make sure size of hostname is not 936 | // broken by replacing non-ASCII by nothing 937 | newpart += 'x'; 938 | } else { 939 | newpart += part[j]; 940 | } 941 | } 942 | // we test again with ASCII char only 943 | if (!newpart.match(hostnamePartPattern)) { 944 | var validParts = hostparts.slice(0, i); 945 | var notHost = hostparts.slice(i + 1); 946 | var bit = part.match(hostnamePartStart); 947 | if (bit) { 948 | validParts.push(bit[1]); 949 | notHost.unshift(bit[2]); 950 | } 951 | if (notHost.length) { 952 | rest = '/' + notHost.join('.') + rest; 953 | } 954 | this$1.hostname = validParts.join('.'); 955 | break; 956 | } 957 | } 958 | } 959 | } 960 | 961 | if (this.hostname.length > hostnameMaxLen) { 962 | this.hostname = ''; 963 | } else { 964 | // hostnames are always lower case. 965 | this.hostname = this.hostname.toLowerCase(); 966 | } 967 | 968 | if (!ipv6Hostname) { 969 | // IDNA Support: Returns a punycoded representation of "domain". 970 | // It only converts parts of the domain name that 971 | // have non-ASCII characters, i.e. it doesn't matter if 972 | // you call it with a domain that already is ASCII-only. 973 | this.hostname = punycode.toASCII(this.hostname); 974 | } 975 | 976 | var p = this.port ? ':' + this.port : ''; 977 | var h = this.hostname || ''; 978 | this.host = h + p; 979 | this.href += this.host; 980 | 981 | // strip [ and ] from the hostname 982 | // the host field still retains them, though 983 | if (ipv6Hostname) { 984 | this.hostname = this.hostname.substr(1, this.hostname.length - 2); 985 | if (rest[0] !== '/') { 986 | rest = '/' + rest; 987 | } 988 | } 989 | } 990 | 991 | // now rest is set to the post-host stuff. 992 | // chop off any delim chars. 993 | if (!unsafeProtocol[lowerProto]) { 994 | 995 | // First, make 100% sure that any "autoEscape" chars get 996 | // escaped, even if encodeURIComponent doesn't think they 997 | // need to be. 998 | for (var i = 0, l = autoEscape.length; i < l; i++) { 999 | var ae = autoEscape[i]; 1000 | if (rest.indexOf(ae) === -1) 1001 | { continue; } 1002 | var esc = encodeURIComponent(ae); 1003 | if (esc === ae) { 1004 | esc = escape(ae); 1005 | } 1006 | rest = rest.split(ae).join(esc); 1007 | } 1008 | } 1009 | 1010 | 1011 | // chop off from the tail first. 1012 | var hash = rest.indexOf('#'); 1013 | if (hash !== -1) { 1014 | // got a fragment string. 1015 | this.hash = rest.substr(hash); 1016 | rest = rest.slice(0, hash); 1017 | } 1018 | var qm = rest.indexOf('?'); 1019 | if (qm !== -1) { 1020 | this.search = rest.substr(qm); 1021 | this.query = rest.substr(qm + 1); 1022 | if (parseQueryString) { 1023 | this.query = querystring.parse(this.query); 1024 | } 1025 | rest = rest.slice(0, qm); 1026 | } else if (parseQueryString) { 1027 | // no query string, but parseQueryString still requested 1028 | this.search = ''; 1029 | this.query = {}; 1030 | } 1031 | if (rest) { this.pathname = rest; } 1032 | if (slashedProtocol[lowerProto] && 1033 | this.hostname && !this.pathname) { 1034 | this.pathname = '/'; 1035 | } 1036 | 1037 | //to support http.request 1038 | if (this.pathname || this.search) { 1039 | var p = this.pathname || ''; 1040 | var s = this.search || ''; 1041 | this.path = p + s; 1042 | } 1043 | 1044 | // finally, reconstruct the href based on what has been validated. 1045 | this.href = this.format(); 1046 | return this; 1047 | }; 1048 | 1049 | // format a parsed object into a url string 1050 | function urlFormat(obj) { 1051 | // ensure it's an object, and not a string url. 1052 | // If it's an obj, this is a no-op. 1053 | // this way, you can call url_format() on strings 1054 | // to clean up potentially wonky urls. 1055 | if (util.isString(obj)) { obj = urlParse(obj); } 1056 | if (!(obj instanceof Url)) { return Url.prototype.format.call(obj); } 1057 | return obj.format(); 1058 | } 1059 | 1060 | Url.prototype.format = function() { 1061 | var auth = this.auth || ''; 1062 | if (auth) { 1063 | auth = encodeURIComponent(auth); 1064 | auth = auth.replace(/%3A/i, ':'); 1065 | auth += '@'; 1066 | } 1067 | 1068 | var protocol = this.protocol || '', 1069 | pathname = this.pathname || '', 1070 | hash = this.hash || '', 1071 | host = false, 1072 | query = ''; 1073 | 1074 | if (this.host) { 1075 | host = auth + this.host; 1076 | } else if (this.hostname) { 1077 | host = auth + (this.hostname.indexOf(':') === -1 ? 1078 | this.hostname : 1079 | '[' + this.hostname + ']'); 1080 | if (this.port) { 1081 | host += ':' + this.port; 1082 | } 1083 | } 1084 | 1085 | if (this.query && 1086 | util.isObject(this.query) && 1087 | Object.keys(this.query).length) { 1088 | query = querystring.stringify(this.query); 1089 | } 1090 | 1091 | var search = this.search || (query && ('?' + query)) || ''; 1092 | 1093 | if (protocol && protocol.substr(-1) !== ':') { protocol += ':'; } 1094 | 1095 | // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. 1096 | // unless they had them to begin with. 1097 | if (this.slashes || 1098 | (!protocol || slashedProtocol[protocol]) && host !== false) { 1099 | host = '//' + (host || ''); 1100 | if (pathname && pathname.charAt(0) !== '/') { pathname = '/' + pathname; } 1101 | } else if (!host) { 1102 | host = ''; 1103 | } 1104 | 1105 | if (hash && hash.charAt(0) !== '#') { hash = '#' + hash; } 1106 | if (search && search.charAt(0) !== '?') { search = '?' + search; } 1107 | 1108 | pathname = pathname.replace(/[?#]/g, function(match) { 1109 | return encodeURIComponent(match); 1110 | }); 1111 | search = search.replace('#', '%23'); 1112 | 1113 | return protocol + host + pathname + search + hash; 1114 | }; 1115 | 1116 | function urlResolve(source, relative) { 1117 | return urlParse(source, false, true).resolve(relative); 1118 | } 1119 | 1120 | Url.prototype.resolve = function(relative) { 1121 | return this.resolveObject(urlParse(relative, false, true)).format(); 1122 | }; 1123 | 1124 | function urlResolveObject(source, relative) { 1125 | if (!source) { return relative; } 1126 | return urlParse(source, false, true).resolveObject(relative); 1127 | } 1128 | 1129 | Url.prototype.resolveObject = function(relative) { 1130 | var this$1 = this; 1131 | 1132 | if (util.isString(relative)) { 1133 | var rel = new Url(); 1134 | rel.parse(relative, false, true); 1135 | relative = rel; 1136 | } 1137 | 1138 | var result = new Url(); 1139 | var tkeys = Object.keys(this); 1140 | for (var tk = 0; tk < tkeys.length; tk++) { 1141 | var tkey = tkeys[tk]; 1142 | result[tkey] = this$1[tkey]; 1143 | } 1144 | 1145 | // hash is always overridden, no matter what. 1146 | // even href="" will remove it. 1147 | result.hash = relative.hash; 1148 | 1149 | // if the relative url is empty, then there's nothing left to do here. 1150 | if (relative.href === '') { 1151 | result.href = result.format(); 1152 | return result; 1153 | } 1154 | 1155 | // hrefs like //foo/bar always cut to the protocol. 1156 | if (relative.slashes && !relative.protocol) { 1157 | // take everything except the protocol from relative 1158 | var rkeys = Object.keys(relative); 1159 | for (var rk = 0; rk < rkeys.length; rk++) { 1160 | var rkey = rkeys[rk]; 1161 | if (rkey !== 'protocol') 1162 | { result[rkey] = relative[rkey]; } 1163 | } 1164 | 1165 | //urlParse appends trailing / to urls like http://www.example.com 1166 | if (slashedProtocol[result.protocol] && 1167 | result.hostname && !result.pathname) { 1168 | result.path = result.pathname = '/'; 1169 | } 1170 | 1171 | result.href = result.format(); 1172 | return result; 1173 | } 1174 | 1175 | if (relative.protocol && relative.protocol !== result.protocol) { 1176 | // if it's a known url protocol, then changing 1177 | // the protocol does weird things 1178 | // first, if it's not file:, then we MUST have a host, 1179 | // and if there was a path 1180 | // to begin with, then we MUST have a path. 1181 | // if it is file:, then the host is dropped, 1182 | // because that's known to be hostless. 1183 | // anything else is assumed to be absolute. 1184 | if (!slashedProtocol[relative.protocol]) { 1185 | var keys = Object.keys(relative); 1186 | for (var v = 0; v < keys.length; v++) { 1187 | var k = keys[v]; 1188 | result[k] = relative[k]; 1189 | } 1190 | result.href = result.format(); 1191 | return result; 1192 | } 1193 | 1194 | result.protocol = relative.protocol; 1195 | if (!relative.host && !hostlessProtocol[relative.protocol]) { 1196 | var relPath = (relative.pathname || '').split('/'); 1197 | while (relPath.length && !(relative.host = relPath.shift())){ } 1198 | if (!relative.host) { relative.host = ''; } 1199 | if (!relative.hostname) { relative.hostname = ''; } 1200 | if (relPath[0] !== '') { relPath.unshift(''); } 1201 | if (relPath.length < 2) { relPath.unshift(''); } 1202 | result.pathname = relPath.join('/'); 1203 | } else { 1204 | result.pathname = relative.pathname; 1205 | } 1206 | result.search = relative.search; 1207 | result.query = relative.query; 1208 | result.host = relative.host || ''; 1209 | result.auth = relative.auth; 1210 | result.hostname = relative.hostname || relative.host; 1211 | result.port = relative.port; 1212 | // to support http.request 1213 | if (result.pathname || result.search) { 1214 | var p = result.pathname || ''; 1215 | var s = result.search || ''; 1216 | result.path = p + s; 1217 | } 1218 | result.slashes = result.slashes || relative.slashes; 1219 | result.href = result.format(); 1220 | return result; 1221 | } 1222 | 1223 | var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), 1224 | isRelAbs = ( 1225 | relative.host || 1226 | relative.pathname && relative.pathname.charAt(0) === '/' 1227 | ), 1228 | mustEndAbs = (isRelAbs || isSourceAbs || 1229 | (result.host && relative.pathname)), 1230 | removeAllDots = mustEndAbs, 1231 | srcPath = result.pathname && result.pathname.split('/') || [], 1232 | relPath = relative.pathname && relative.pathname.split('/') || [], 1233 | psychotic = result.protocol && !slashedProtocol[result.protocol]; 1234 | 1235 | // if the url is a non-slashed url, then relative 1236 | // links like ../.. should be able 1237 | // to crawl up to the hostname, as well. This is strange. 1238 | // result.protocol has already been set by now. 1239 | // Later on, put the first path part into the host field. 1240 | if (psychotic) { 1241 | result.hostname = ''; 1242 | result.port = null; 1243 | if (result.host) { 1244 | if (srcPath[0] === '') { srcPath[0] = result.host; } 1245 | else { srcPath.unshift(result.host); } 1246 | } 1247 | result.host = ''; 1248 | if (relative.protocol) { 1249 | relative.hostname = null; 1250 | relative.port = null; 1251 | if (relative.host) { 1252 | if (relPath[0] === '') { relPath[0] = relative.host; } 1253 | else { relPath.unshift(relative.host); } 1254 | } 1255 | relative.host = null; 1256 | } 1257 | mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); 1258 | } 1259 | 1260 | if (isRelAbs) { 1261 | // it's absolute. 1262 | result.host = (relative.host || relative.host === '') ? 1263 | relative.host : result.host; 1264 | result.hostname = (relative.hostname || relative.hostname === '') ? 1265 | relative.hostname : result.hostname; 1266 | result.search = relative.search; 1267 | result.query = relative.query; 1268 | srcPath = relPath; 1269 | // fall through to the dot-handling below. 1270 | } else if (relPath.length) { 1271 | // it's relative 1272 | // throw away the existing file, and take the new path instead. 1273 | if (!srcPath) { srcPath = []; } 1274 | srcPath.pop(); 1275 | srcPath = srcPath.concat(relPath); 1276 | result.search = relative.search; 1277 | result.query = relative.query; 1278 | } else if (!util.isNullOrUndefined(relative.search)) { 1279 | // just pull out the search. 1280 | // like href='?foo'. 1281 | // Put this after the other two cases because it simplifies the booleans 1282 | if (psychotic) { 1283 | result.hostname = result.host = srcPath.shift(); 1284 | //occationaly the auth can get stuck only in host 1285 | //this especially happens in cases like 1286 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1287 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1288 | result.host.split('@') : false; 1289 | if (authInHost) { 1290 | result.auth = authInHost.shift(); 1291 | result.host = result.hostname = authInHost.shift(); 1292 | } 1293 | } 1294 | result.search = relative.search; 1295 | result.query = relative.query; 1296 | //to support http.request 1297 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1298 | result.path = (result.pathname ? result.pathname : '') + 1299 | (result.search ? result.search : ''); 1300 | } 1301 | result.href = result.format(); 1302 | return result; 1303 | } 1304 | 1305 | if (!srcPath.length) { 1306 | // no path at all. easy. 1307 | // we've already handled the other stuff above. 1308 | result.pathname = null; 1309 | //to support http.request 1310 | if (result.search) { 1311 | result.path = '/' + result.search; 1312 | } else { 1313 | result.path = null; 1314 | } 1315 | result.href = result.format(); 1316 | return result; 1317 | } 1318 | 1319 | // if a url ENDs in . or .., then it must get a trailing slash. 1320 | // however, if it ends in anything else non-slashy, 1321 | // then it must NOT get a trailing slash. 1322 | var last = srcPath.slice(-1)[0]; 1323 | var hasTrailingSlash = ( 1324 | (result.host || relative.host || srcPath.length > 1) && 1325 | (last === '.' || last === '..') || last === ''); 1326 | 1327 | // strip single dots, resolve double dots to parent dir 1328 | // if the path tries to go above the root, `up` ends up > 0 1329 | var up = 0; 1330 | for (var i = srcPath.length; i >= 0; i--) { 1331 | last = srcPath[i]; 1332 | if (last === '.') { 1333 | srcPath.splice(i, 1); 1334 | } else if (last === '..') { 1335 | srcPath.splice(i, 1); 1336 | up++; 1337 | } else if (up) { 1338 | srcPath.splice(i, 1); 1339 | up--; 1340 | } 1341 | } 1342 | 1343 | // if the path is allowed to go above the root, restore leading ..s 1344 | if (!mustEndAbs && !removeAllDots) { 1345 | for (; up--; up) { 1346 | srcPath.unshift('..'); 1347 | } 1348 | } 1349 | 1350 | if (mustEndAbs && srcPath[0] !== '' && 1351 | (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { 1352 | srcPath.unshift(''); 1353 | } 1354 | 1355 | if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { 1356 | srcPath.push(''); 1357 | } 1358 | 1359 | var isAbsolute = srcPath[0] === '' || 1360 | (srcPath[0] && srcPath[0].charAt(0) === '/'); 1361 | 1362 | // put the host back 1363 | if (psychotic) { 1364 | result.hostname = result.host = isAbsolute ? '' : 1365 | srcPath.length ? srcPath.shift() : ''; 1366 | //occationaly the auth can get stuck only in host 1367 | //this especially happens in cases like 1368 | //url.resolveObject('mailto:local1@domain1', 'local2@domain2') 1369 | var authInHost = result.host && result.host.indexOf('@') > 0 ? 1370 | result.host.split('@') : false; 1371 | if (authInHost) { 1372 | result.auth = authInHost.shift(); 1373 | result.host = result.hostname = authInHost.shift(); 1374 | } 1375 | } 1376 | 1377 | mustEndAbs = mustEndAbs || (result.host && srcPath.length); 1378 | 1379 | if (mustEndAbs && !isAbsolute) { 1380 | srcPath.unshift(''); 1381 | } 1382 | 1383 | if (!srcPath.length) { 1384 | result.pathname = null; 1385 | result.path = null; 1386 | } else { 1387 | result.pathname = srcPath.join('/'); 1388 | } 1389 | 1390 | //to support request.http 1391 | if (!util.isNull(result.pathname) || !util.isNull(result.search)) { 1392 | result.path = (result.pathname ? result.pathname : '') + 1393 | (result.search ? result.search : ''); 1394 | } 1395 | result.auth = relative.auth || result.auth; 1396 | result.slashes = result.slashes || relative.slashes; 1397 | result.href = result.format(); 1398 | return result; 1399 | }; 1400 | 1401 | Url.prototype.parseHost = function() { 1402 | var host = this.host; 1403 | var port = portPattern.exec(host); 1404 | if (port) { 1405 | port = port[0]; 1406 | if (port !== ':') { 1407 | this.port = port.substr(1); 1408 | } 1409 | host = host.substr(0, host.length - port.length); 1410 | } 1411 | if (host) { this.hostname = host; } 1412 | }; 1413 | 1414 | var url = { 1415 | parse: parse, 1416 | resolve: resolve, 1417 | resolveObject: resolveObject, 1418 | format: format, 1419 | Url: Url_1 1420 | }; 1421 | 1422 | function install (Vue, options) { 1423 | if (install.installed) { return } 1424 | install.installed = true; 1425 | 1426 | Object.defineProperty(Vue.prototype, '$wechatAuth', { 1427 | get: function get () { return this.$root._wechatAuth } 1428 | }); 1429 | 1430 | Vue.mixin({ 1431 | beforeCreate: function beforeCreate () { 1432 | if (this.$options.wechatAuth) { 1433 | this._wechatAuth = this.$options.wechatAuth; 1434 | } 1435 | } 1436 | }); 1437 | } 1438 | 1439 | var config = { 1440 | git: 'https://github.com/raychenfj/v-wechat-auth' 1441 | }; 1442 | 1443 | var requiredProps = ['appId', 'scope', 'authorize']; 1444 | var defaultOptions = { 1445 | autoRedirect: true, 1446 | state: '', 1447 | authorize: function authorize () { 1448 | console.error('should implement authorize method in options'); 1449 | } 1450 | }; 1451 | 1452 | function isFunction (fn) { 1453 | return fn && typeof fn === 'function' 1454 | } 1455 | 1456 | var WechatAuth = function WechatAuth (options) { 1457 | var this$1 = this; 1458 | 1459 | this.options = Object.assign(defaultOptions, options); 1460 | 1461 | requiredProps.forEach(function (prop) { 1462 | if (!this$1.options[prop]) { 1463 | console.error(("required property " + prop + " is missing in options, please visit " + (config.git) + " for more info.")); 1464 | } 1465 | }); 1466 | 1467 | this.user = null; 1468 | }; 1469 | 1470 | /** 1471 | * authorize 1472 | * @param {*} onSuccess 1473 | * @param {*} onFail 1474 | * @returns {Promise} 1475 | */ 1476 | WechatAuth.prototype.authorize = function authorize (onSuccess, onFail) { 1477 | var this$1 = this; 1478 | 1479 | var urlObj = url.parse(window.location.href, true); 1480 | 1481 | if (urlObj.query && !urlObj.query.code) { 1482 | // delete state in query in url 1483 | delete urlObj.query.state; 1484 | delete urlObj.search; 1485 | return this.redirect(url.format(urlObj)) 1486 | } 1487 | 1488 | // decorated success 1489 | var success = function (data) { 1490 | var user = this$1.onSuccess(data); 1491 | if (isFunction(onSuccess)) { 1492 | onSuccess(user); 1493 | } 1494 | return user 1495 | }; 1496 | 1497 | // decorated fail 1498 | var fail = function (e) { 1499 | this$1.onFail(e); 1500 | if (isFunction(onFail)) { 1501 | onFail(e); 1502 | } 1503 | }; 1504 | 1505 | try { 1506 | // if options.authorize use callback 1507 | var promise = this.options.authorize(urlObj.query.code, success, fail); 1508 | 1509 | // if options.authorize return promise 1510 | if (promise && promise instanceof Promise) { 1511 | return promise.then(success).catch(fail) 1512 | } 1513 | } catch (e) { 1514 | fail(e); 1515 | } 1516 | }; 1517 | 1518 | /** 1519 | * onSuccess 1520 | * @private 1521 | * @param {*} data 1522 | */ 1523 | WechatAuth.prototype.onSuccess = function onSuccess (data) { 1524 | if (!data.openid && this.options.autoRedirect) { 1525 | var urlObj = url.parse(window.location.href, true); 1526 | // delete code and state in query in url 1527 | delete urlObj.query.code; 1528 | delete urlObj.query.state; 1529 | delete urlObj.search; 1530 | return this.redirect(url.format(urlObj)) 1531 | } 1532 | this.user = data; 1533 | return this.user 1534 | }; 1535 | 1536 | /** 1537 | * onFail 1538 | * @private 1539 | * @param {*} e 1540 | */ 1541 | WechatAuth.prototype.onFail = function onFail (e) { 1542 | console.error('error occurs when authorize from back end'); 1543 | console.error(e); 1544 | }; 1545 | 1546 | /** 1547 | * redirect to wechat auth url 1548 | * @param {*} url 1549 | */ 1550 | WechatAuth.prototype.redirect = function redirect (url$$1) { 1551 | var options = this.options; 1552 | window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + (options.appId) + "&redirect_uri=" + (encodeURIComponent(url$$1)) + "&response_type=code&scope=" + (options.scope) + "&state=" + (options.state) + "#wechat_redirect"; 1553 | }; 1554 | 1555 | WechatAuth.install = install; 1556 | 1557 | return WechatAuth; 1558 | 1559 | }))); 1560 | -------------------------------------------------------------------------------- /dist/v-wechat-auth.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * v-wechat-auth v1.0.0 3 | * (c) 2018 fengjun.chen 4 | * Released under the MIT License. 5 | */ 6 | 7 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.VWechatAuth=e()}(this,function(){"use strict";var t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t,e){return t(e={exports:{}},e.exports),e.exports}var r=e(function(e,r){!function(o){var n=r&&!r.nodeType&&r,s=e&&!e.nodeType&&e,h="object"==typeof t&&t;h.global!==h&&h.window!==h&&h.self!==h||(o=h);var i,a,u=2147483647,c=36,l=1,p=26,f=38,m=700,d=72,v=128,y="-",g=/^xn--/,b=/[^\x20-\x7E]/,w=/[\x2E\u3002\uFF0E\uFF61]/g,j={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},x=c-l,O=Math.floor,q=String.fromCharCode;function A(t){throw RangeError(j[t])}function C(t,e){for(var r=t.length,o=[];r--;)o[r]=e(t[r]);return o}function I(t,e){var r=t.split("@"),o="";return r.length>1&&(o=r[0]+"@",t=r[1]),o+C((t=t.replace(w,".")).split("."),e).join(".")}function U(t){for(var e,r,o=[],n=0,s=t.length;n=55296&&e<=56319&&n65535&&(e+=q((t-=65536)>>>10&1023|55296),t=56320|1023&t),e+=q(t)}).join("")}function z(t,e){return t+22+75*(t<26)-((0!=e)<<5)}function S(t,e,r){var o=0;for(t=r?O(t/m):t>>1,t+=O(t/e);t>x*p>>1;o+=c)t=O(t/x);return O(o+(x+1)*t/(t+f))}function $(t){var e,r,o,n,s,h,i,a,f,m,g,b=[],w=t.length,j=0,x=v,q=d;for((r=t.lastIndexOf(y))<0&&(r=0),o=0;o=128&&A("not-basic"),b.push(t.charCodeAt(o));for(n=r>0?r+1:0;n=w&&A("invalid-input"),((a=(g=t.charCodeAt(n++))-48<10?g-22:g-65<26?g-65:g-97<26?g-97:c)>=c||a>O((u-j)/h))&&A("overflow"),j+=a*h,!(a<(f=i<=q?l:i>=q+p?p:i-q));i+=c)h>O(u/(m=c-f))&&A("overflow"),h*=m;q=S(j-s,e=b.length+1,0==s),O(j/e)>u-x&&A("overflow"),x+=O(j/e),j%=e,b.splice(j++,0,x)}return R(b)}function k(t){var e,r,o,n,s,h,i,a,f,m,g,b,w,j,x,C=[];for(b=(t=U(t)).length,e=v,r=0,s=d,h=0;h=e&&gO((u-r)/(w=o+1))&&A("overflow"),r+=(i-e)*w,e=i,h=0;hu&&A("overflow"),g==e){for(a=r,f=c;!(a<(m=f<=s?l:f>=s+p?p:f-s));f+=c)x=a-m,j=c-m,C.push(q(z(m+x%j,0))),a=O(x/j);C.push(q(z(a,0))),s=S(r,w,o==n),r=0,++o}++r,++e}return C.join("")}if(i={version:"1.3.2",ucs2:{decode:U,encode:R},decode:$,encode:k,toASCII:function(t){return I(t,function(t){return b.test(t)?"xn--"+k(t):t})},toUnicode:function(t){return I(t,function(t){return g.test(t)?$(t.slice(4).toLowerCase()):t})}},n&&s)if(e.exports==n)s.exports=i;else for(a in i)i.hasOwnProperty(a)&&(n[a]=i[a]);else o.punycode=i}(t)}),o={isString:function(t){return"string"==typeof t},isObject:function(t){return"object"==typeof t&&null!==t},isNull:function(t){return null===t},isNullOrUndefined:function(t){return null==t}};var n=function(t,e,r,o){e=e||"&",r=r||"=";var n={};if("string"!=typeof t||0===t.length)return n;var s=/\+/g;t=t.split(e);var h=1e3;o&&"number"==typeof o.maxKeys&&(h=o.maxKeys);var i,a,u=t.length;h>0&&u>h&&(u=h);for(var c=0;c=0?(l=d.substr(0,v),p=d.substr(v+1)):(l=d,p=""),f=decodeURIComponent(l),m=decodeURIComponent(p),i=n,a=f,Object.prototype.hasOwnProperty.call(i,a)?Array.isArray(n[f])?n[f].push(m):n[f]=[n[f],m]:n[f]=m}return n},s=function(t){switch(typeof t){case"string":return t;case"boolean":return t?"true":"false";case"number":return isFinite(t)?t:"";default:return""}},h=function(t,e,r,o){return e=e||"&",r=r||"=",null===t&&(t=void 0),"object"==typeof t?Object.keys(t).map(function(o){var n=encodeURIComponent(s(o))+r;return Array.isArray(t[o])?t[o].map(function(t){return n+encodeURIComponent(s(t))}).join(e):n+encodeURIComponent(s(t[o]))}).join(e):o?encodeURIComponent(s(o))+r+encodeURIComponent(s(t)):""},i=e(function(t,e){e.decode=e.parse=n,e.encode=e.stringify=h}),a=C,u=function(t,e){return C(t,!1,!0).resolve(e)},c=function(t,e){if(!t)return e;return C(t,!1,!0).resolveObject(e)},l=function(t){o.isString(t)&&(t=C(t));if(!(t instanceof f))return f.prototype.format.call(t);return t.format()},p=f;function f(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}var m=/^([a-z0-9.+-]+:)/i,d=/:[0-9]*$/,v=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,y=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),g=["'"].concat(y),b=["%","/","?",";","#"].concat(g),w=["/","?","#"],j=/^[+a-z0-9A-Z_-]{0,63}$/,x=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,O={javascript:!0,"javascript:":!0},q={javascript:!0,"javascript:":!0},A={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function C(t,e,r){if(t&&o.isObject(t)&&t instanceof f)return t;var n=new f;return n.parse(t,e,r),n}f.prototype.parse=function(t,e,n){if(!o.isString(t))throw new TypeError("Parameter 'url' must be a string, not "+typeof t);var s=t.indexOf("?"),h=-1!==s&&s127?k+="x":k+=$[F];if(!k.match(j)){var _=z.slice(0,I),E=z.slice(I+1),P=$.match(x);P&&(_.push(P[1]),E.unshift(P[2])),E.length&&(u="/"+E.join(".")+u),this.hostname=_.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),R||(this.hostname=r.toASCII(this.hostname));var L=this.port?":"+this.port:"",T=this.hostname||"";this.host=T+L,this.href+=this.host,R&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==u[0]&&(u="/"+u))}if(!O[p])for(I=0,S=g.length;I0)&&r.host.split("@"))&&(r.auth=R.shift(),r.host=r.hostname=R.shift());return r.search=t.search,r.query=t.query,o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.href=r.format(),r}if(!j.length)return r.pathname=null,r.search?r.path="/"+r.search:r.path=null,r.href=r.format(),r;for(var O=j.slice(-1)[0],C=(r.host||t.host||j.length>1)&&("."===O||".."===O)||""===O,I=0,U=j.length;U>=0;U--)"."===(O=j[U])?j.splice(U,1):".."===O?(j.splice(U,1),I++):I&&(j.splice(U,1),I--);if(!b&&!w)for(;I--;I)j.unshift("..");!b||""===j[0]||j[0]&&"/"===j[0].charAt(0)||j.unshift(""),C&&"/"!==j.join("/").substr(-1)&&j.push("");var R,z=""===j[0]||j[0]&&"/"===j[0].charAt(0);x&&(r.hostname=r.host=z?"":j.length?j.shift():"",(R=!!(r.host&&r.host.indexOf("@")>0)&&r.host.split("@"))&&(r.auth=R.shift(),r.host=r.hostname=R.shift()));return(b=b||r.host&&j.length)&&!z&&j.unshift(""),j.length?r.pathname=j.join("/"):(r.pathname=null,r.path=null),o.isNull(r.pathname)&&o.isNull(r.search)||(r.path=(r.pathname?r.pathname:"")+(r.search?r.search:"")),r.auth=t.auth||r.auth,r.slashes=r.slashes||t.slashes,r.href=r.format(),r},f.prototype.parseHost=function(){var t=this.host,e=d.exec(t);e&&(":"!==(e=e[0])&&(this.port=e.substr(1)),t=t.substr(0,t.length-e.length)),t&&(this.hostname=t)};var I={parse:a,resolve:u,resolveObject:c,format:l,Url:p};var U="https://github.com/raychenfj/v-wechat-auth",R=["appId","scope","authorize"],z={autoRedirect:!0,state:"",authorize:function(){console.error("should implement authorize method in options")}};function S(t){return t&&"function"==typeof t}var $=function(t){var e=this;this.options=Object.assign(z,t),R.forEach(function(t){e.options[t]||console.error("required property "+t+" is missing in options, please visit "+U+" for more info.")}),this.user=null};return $.prototype.authorize=function(t,e){var r=this,o=I.parse(window.location.href,!0);if(o.query&&!o.query.code)return delete o.query.state,delete o.search,this.redirect(I.format(o));var n=function(e){var o=r.onSuccess(e);return S(t)&&t(o),o},s=function(t){r.onFail(t),S(e)&&e(t)};try{var h=this.options.authorize(o.query.code,n,s);if(h&&h instanceof Promise)return h.then(n).catch(s)}catch(t){s(t)}},$.prototype.onSuccess=function(t){if(!t.openid&&this.options.autoRedirect){var e=I.parse(window.location.href,!0);return delete e.query.code,delete e.query.state,delete e.search,this.redirect(I.format(e))}return this.user=t,this.user},$.prototype.onFail=function(t){console.error("error occurs when authorize from back end"),console.error(t)},$.prototype.redirect=function(t){var e=this.options;window.location.href="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+e.appId+"&redirect_uri="+encodeURIComponent(t)+"&response_type=code&scope="+e.scope+"&state="+e.state+"#wechat_redirect"},$.install=function t(e,r){t.installed||(t.installed=!0,Object.defineProperty(e.prototype,"$wechatAuth",{get:function(){return this.$root._wechatAuth}}),e.mixin({beforeCreate:function(){this.$options.wechatAuth&&(this._wechatAuth=this.$options.wechatAuth)}}))},$}); -------------------------------------------------------------------------------- /examples/config.example.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | appId: '', // your wechat appid, 3 | scope: 'snsapi_userinfo', // snsapi_base or snsapi_userinfo 4 | /** 5 | * 6 | * @param {*} code 7 | * @param {*} success if you use callback, don't return anything in the function, and call success and pass response data to it 8 | * @param {*} fail 9 | */ 10 | authorize (code, success, fail) { 11 | return axios.get('your backend api here', { params: { code: code } }) 12 | .then(function (res) { 13 | var data = (res && res.data) || {} // response data should at least contain openid 14 | return data 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v-wechat-auth example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | your openid is: {{user.openid}} 15 |
16 | 17 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /images/openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raychenfj/v-wechat-auth/42e6e1b6f5070b6adecbec7b59b4d22bc423a836/images/openid.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-wechat-auth", 3 | "version": "1.0.0", 4 | "description": "wechat auth plugin for vue 2.0", 5 | "author": "fengjun.chen ", 6 | "main": "dist/v-wechat-auth.common.js", 7 | "module": "dist/v-wechat-auth.esm.js", 8 | "browser": "dist/v-wechat-auth.js", 9 | "unpkg": "dist/v-wechat-auth.js", 10 | "style": "dist/v-wechat-auth.css", 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "scripts": { 16 | "clean": "rimraf dist", 17 | "build": "node build/build.js", 18 | "build:dll": "webpack --progress --config build/webpack.config.dll.js", 19 | "lint": "yon run lint:js && yon run lint:css", 20 | "lint:js": "eslint --ext js --ext jsx --ext vue src test/**/*.spec.js test/*.js build", 21 | "lint:js:fix": "yon run lint:js -- --fix", 22 | "lint:css": "stylelint src/**/*.{vue,css}", 23 | "lint:staged": "lint-staged", 24 | "pretest": "yon run lint", 25 | "test": "cross-env BABEL_ENV=test karma start test/karma.conf.js --single-run", 26 | "dev": "webpack-dashboard -- webpack-dev-server --config build/webpack.config.dev.js --open", 27 | "dev:coverage": "cross-env BABEL_ENV=test karma start test/karma.conf.js", 28 | "prepublish": "yon run build", 29 | "example": "sudo lite-server -c ./bs-config.json" 30 | }, 31 | "lint-staged": { 32 | "*.{vue,jsx,js}": [ 33 | "eslint --fix" 34 | ], 35 | "*.{vue,css}": [ 36 | "stylefmt", 37 | "stylelint" 38 | ] 39 | }, 40 | "pre-commit": "lint:staged", 41 | "devDependencies": { 42 | "add-asset-html-webpack-plugin": "^2.0.0", 43 | "babel-core": "^6.24.0", 44 | "babel-eslint": "^7.2.0", 45 | "babel-helper-vue-jsx-merge-props": "^2.0.0", 46 | "babel-loader": "^7.0.0", 47 | "babel-plugin-external-helpers": "^6.22.0", 48 | "babel-plugin-istanbul": "^4.1.0", 49 | "babel-plugin-syntax-jsx": "^6.18.0", 50 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 51 | "babel-plugin-transform-runtime": "^6.23.0", 52 | "babel-plugin-transform-vue-jsx": "^3.4.0", 53 | "babel-preset-env": "^1.4.0", 54 | "buble": "^0.15.2", 55 | "chai": "^3.5.0", 56 | "chai-dom": "^1.4.0", 57 | "clean-css": "^4.0.0", 58 | "cross-env": "^4.0.0", 59 | "css-loader": "^0.28.0", 60 | "eslint": "^3.19.0", 61 | "eslint-config-vue": "^2.0.0", 62 | "eslint-plugin-vue": "^2.0.0", 63 | "extract-text-webpack-plugin": "^2.1.0", 64 | "html-webpack-plugin": "^2.28.0", 65 | "karma": "^1.7.0", 66 | "karma-chai-dom": "^1.1.0", 67 | "karma-chrome-launcher": "^2.1.0", 68 | "karma-coverage": "^1.1.0", 69 | "karma-mocha": "^1.3.0", 70 | "karma-sinon-chai": "^1.3.0", 71 | "karma-sourcemap-loader": "^0.3.7", 72 | "karma-spec-reporter": "^0.0.31", 73 | "karma-webpack": "^2.0.0", 74 | "lint-staged": "^3.4.0", 75 | "lite-server": "^2.3.0", 76 | "mkdirp": "^0.5.1", 77 | "mocha": "^3.3.0", 78 | "mocha-css": "^1.0.1", 79 | "postcss": "^6.0.0", 80 | "postcss-cssnext": "^2.10.0", 81 | "pre-commit": "^1.2.0", 82 | "rimraf": "^2.6.0", 83 | "rollup": "^0.41.6", 84 | "rollup-plugin-buble": "^0.15.0", 85 | "rollup-plugin-commonjs": "^8.0.0", 86 | "rollup-plugin-jsx": "^1.0.0", 87 | "rollup-plugin-node-resolve": "^3.0.0", 88 | "rollup-plugin-postcss": "^0.4.1", 89 | "rollup-plugin-replace": "^1.1.0", 90 | "rollup-plugin-vue": "^2.3.0", 91 | "sinon": "2.2.0", 92 | "sinon-chai": "^2.10.0", 93 | "style-loader": "^0.17.0", 94 | "stylefmt": "^5.3.0", 95 | "stylelint": "^7.10.0", 96 | "stylelint-config-standard": "^16.0.0", 97 | "stylelint-processor-html": "^1.0.0", 98 | "uglify-js": "^3.0.0", 99 | "uppercamelcase": "^3.0.0", 100 | "vue": "^2.3.0", 101 | "vue-loader": "^12.0.0", 102 | "vue-template-compiler": "^2.3.0", 103 | "webpack": "^2.5.0", 104 | "webpack-bundle-analyzer": "^2.4.0", 105 | "webpack-dashboard": "^0.4.0", 106 | "webpack-dev-server": "^2.4.0", 107 | "webpack-merge": "^4.0.0", 108 | "yarn-or-npm": "^2.0.0" 109 | }, 110 | "peerDependencies": { 111 | "vue": "^2.3.0" 112 | }, 113 | "dllPlugin": { 114 | "name": "vuePluginTemplateDeps", 115 | "include": [ 116 | "mocha/mocha.js", 117 | "style-loader!css-loader!mocha-css", 118 | "html-entities", 119 | "vue/dist/vue.js", 120 | "chai", 121 | "core-js/library", 122 | "url", 123 | "sockjs-client", 124 | "vue-style-loader/lib/addStylesClient.js", 125 | "events", 126 | "ansi-html", 127 | "style-loader/addStyles.js" 128 | ] 129 | }, 130 | "repository": { 131 | "type": "git", 132 | "url": "git+https://github.com/raychenfj/v-wechat-auth.git" 133 | }, 134 | "bugs": { 135 | "url": "https://github.com/raychenfj/v-wechat-auth/issues" 136 | }, 137 | "homepage": "https://github.com/raychenfj/v-wechat-auth#readme", 138 | "license": { 139 | "type": "MIT", 140 | "url": "http://www.opensource.org/licenses/mit-license.php" 141 | }, 142 | "dependencies": { 143 | "url": "^0.11.0" 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | git: 'https://github.com/raychenfj/v-wechat-auth' 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import url from 'url' 2 | 3 | import install from './install' 4 | import config from './config' 5 | 6 | const requiredProps = ['appId', 'scope', 'authorize'] 7 | const defaultOptions = { 8 | autoRedirect: true, 9 | state: '', 10 | authorize () { 11 | console.error('should implement authorize method in options') 12 | } 13 | } 14 | 15 | function isFunction (fn) { 16 | return fn && typeof fn === 'function' 17 | } 18 | 19 | export default class WechatAuth { 20 | /** 21 | * @typedef {Object} Options wechat auth options 22 | * @property {boolean} autoRedirect optional, auto redirect to wechat oauth url when there is no code in query or no openid in ajax response 23 | * @property {string} appId required, wechat appId 24 | * @property {string} scope required, wechat auth scope, snsapi_base or snsapi_userinfo 25 | * @property {string} state optional, wechat state 26 | * @property {function} authorize required, an ajax call to back end and should return an object contains openid at least, support promise or callback 27 | * @property {boolean} ssr optional, used in server side render, feature not implement yet 28 | */ 29 | 30 | /** 31 | * Create a wechat auth object 32 | * @constructor 33 | * @param {Options} options 34 | */ 35 | constructor (options) { 36 | this.options = Object.assign(defaultOptions, options) 37 | 38 | requiredProps.forEach(prop => { 39 | if (!this.options[prop]) { 40 | console.error(`required property ${prop} is missing in options, please visit ${config.git} for more info.`) 41 | } 42 | }) 43 | 44 | this.user = null 45 | } 46 | 47 | /** 48 | * authorize 49 | * @param {*} onSuccess 50 | * @param {*} onFail 51 | * @returns {Promise} 52 | */ 53 | authorize (onSuccess, onFail) { 54 | const urlObj = url.parse(window.location.href, true) 55 | 56 | if (urlObj.query && !urlObj.query.code) { 57 | // delete state in query in url 58 | delete urlObj.query.state 59 | delete urlObj.search 60 | return this.redirect(url.format(urlObj)) 61 | } 62 | 63 | // decorated success 64 | const success = (data) => { 65 | const user = this.onSuccess(data) 66 | if (isFunction(onSuccess)) { 67 | onSuccess(user) 68 | } 69 | return user 70 | } 71 | 72 | // decorated fail 73 | const fail = (e) => { 74 | this.onFail(e) 75 | if (isFunction(onFail)) { 76 | onFail(e) 77 | } 78 | } 79 | 80 | try { 81 | // if options.authorize use callback 82 | const promise = this.options.authorize(urlObj.query.code, success, fail) 83 | 84 | // if options.authorize return promise 85 | if (promise && promise instanceof Promise) { 86 | return promise.then(success).catch(fail) 87 | } 88 | } catch (e) { 89 | fail(e) 90 | } 91 | } 92 | 93 | /** 94 | * onSuccess 95 | * @private 96 | * @param {*} data 97 | */ 98 | onSuccess (data) { 99 | if (!data.openid && this.options.autoRedirect) { 100 | const urlObj = url.parse(window.location.href, true) 101 | // delete code and state in query in url 102 | delete urlObj.query.code 103 | delete urlObj.query.state 104 | delete urlObj.search 105 | return this.redirect(url.format(urlObj)) 106 | } 107 | this.user = data 108 | return this.user 109 | } 110 | 111 | /** 112 | * onFail 113 | * @private 114 | * @param {*} e 115 | */ 116 | onFail (e) { 117 | console.error('error occurs when authorize from back end') 118 | console.error(e) 119 | } 120 | 121 | /** 122 | * redirect to wechat auth url 123 | * @param {*} url 124 | */ 125 | redirect (url) { 126 | const options = this.options 127 | window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${options.appId}&redirect_uri=${encodeURIComponent(url)}&response_type=code&scope=${options.scope}&state=${options.state}#wechat_redirect` 128 | } 129 | } 130 | 131 | WechatAuth.install = install 132 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | export default function install (Vue, options) { 2 | if (install.installed) return 3 | install.installed = true 4 | 5 | Object.defineProperty(Vue.prototype, '$wechatAuth', { 6 | get () { return this.$root._wechatAuth } 7 | }) 8 | 9 | Vue.mixin({ 10 | beforeCreate () { 11 | if (this.$options.wechatAuth) { 12 | this._wechatAuth = this.$options.wechatAuth 13 | } 14 | } 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/helpers/Test.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 108 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase' 2 | import { createVM, Vue } from './utils' 3 | import { nextTick } from './wait-for-update' 4 | 5 | export function dataPropagationTest (Component) { 6 | return function () { 7 | const spy = sinon.spy() 8 | const vm = createVM(this, function (h) { 9 | return ( 10 | Hello 11 | ) 12 | }) 13 | spy.should.have.not.been.called 14 | vm.$('.custom').should.exist 15 | vm.$('.custom').click() 16 | spy.should.have.been.calledOnce 17 | } 18 | } 19 | 20 | export function attrTest (it, base, Component, attr) { 21 | const attrs = Array.isArray(attr) ? attr : [attr] 22 | 23 | attrs.forEach(attr => { 24 | it(attr, function (done) { 25 | const vm = createVM(this, function (h) { 26 | const opts = { 27 | props: { 28 | [camelcase(attr)]: this.active 29 | } 30 | } 31 | return ( 32 | {attr} 33 | ) 34 | }, { 35 | data: { active: true } 36 | }) 37 | vm.$(`.${base}`).should.have.class(`${base}--${attr}`) 38 | vm.active = false 39 | nextTick().then(() => { 40 | vm.$(`.${base}`).should.not.have.class(`${base}--${attr}`) 41 | vm.active = true 42 | }).then(done) 43 | }) 44 | }) 45 | } 46 | 47 | export { 48 | createVM, 49 | Vue, 50 | nextTick 51 | } 52 | -------------------------------------------------------------------------------- /test/helpers/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue/dist/vue.js' 2 | import Test from './Test.vue' 3 | 4 | Vue.config.productionTip = false 5 | const isKarma = !!window.__karma__ 6 | 7 | export function createVM (context, template, opts = {}) { 8 | return isKarma 9 | ? createKarmaTest(context, template, opts) 10 | : createVisualTest(context, template, opts) 11 | } 12 | 13 | const emptyNodes = document.querySelectorAll('nonexistant') 14 | Vue.prototype.$$ = function $$ (selector) { 15 | const els = document.querySelectorAll(selector) 16 | const vmEls = this.$el.querySelectorAll(selector) 17 | const fn = vmEls.length 18 | ? el => vmEls.find(el) 19 | : el => this.$el === el 20 | const found = Array.from(els).filter(fn) 21 | return found.length 22 | ? found 23 | : emptyNodes 24 | } 25 | 26 | Vue.prototype.$ = function $ (selector) { 27 | const els = document.querySelectorAll(selector) 28 | const vmEl = this.$el.querySelector(selector) 29 | const fn = vmEl 30 | ? el => el === vmEl 31 | : el => el === this.$el 32 | // Allow should chaining for tests 33 | return Array.from(els).find(fn) || emptyNodes 34 | } 35 | 36 | export function createKarmaTest (context, template, opts) { 37 | const el = document.createElement('div') 38 | document.getElementById('tests').appendChild(el) 39 | const render = typeof template === 'string' 40 | ? { template: `
${template}
` } 41 | : { render: template } 42 | return new Vue({ 43 | el, 44 | name: 'Test', 45 | ...render, 46 | ...opts 47 | }) 48 | } 49 | 50 | export function createVisualTest (context, template, opts) { 51 | let vm 52 | if (typeof template === 'string') { 53 | opts.components = opts.components || {} 54 | // Let the user define a test component 55 | if (!opts.components.Test) { 56 | opts.components.Test = Test 57 | } 58 | vm = new Vue({ 59 | name: 'TestContainer', 60 | el: context.DOMElement, 61 | template: `${template}`, 62 | ...opts 63 | }) 64 | } else { 65 | // TODO allow redefinition of Test component 66 | vm = new Vue({ 67 | name: 'TestContainer', 68 | el: context.DOMElement, 69 | render (h) { 70 | return h(Test, { 71 | attrs: { 72 | id: context.DOMElement.id 73 | } 74 | // render the passed component with this scope 75 | }, [template.call(this, h)]) 76 | }, 77 | ...opts 78 | }) 79 | } 80 | 81 | context.DOMElement.vm = vm 82 | return vm 83 | } 84 | 85 | export function register (name, component) { 86 | Vue.component(name, component) 87 | } 88 | 89 | export { isKarma, Vue } 90 | -------------------------------------------------------------------------------- /test/helpers/wait-for-update.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue/dist/vue.js' 2 | 3 | // Testing helper 4 | // nextTick().then(() => { 5 | // 6 | // Automatically waits for nextTick 7 | // }).then(() => { 8 | // return a promise or value to skip the wait 9 | // }) 10 | function nextTick () { 11 | const jobs = [] 12 | let done 13 | 14 | const chainer = { 15 | then (cb) { 16 | jobs.push(cb) 17 | return chainer 18 | } 19 | } 20 | 21 | function shift (...args) { 22 | const job = jobs.shift() 23 | let result 24 | try { 25 | result = job(...args) 26 | } catch (e) { 27 | jobs.length = 0 28 | done(e) 29 | } 30 | 31 | // wait for nextTick 32 | if (result !== undefined) { 33 | if (result.then) { 34 | result.then(shift) 35 | } else { 36 | shift(result) 37 | } 38 | } else if (jobs.length) { 39 | requestAnimationFrame(() => Vue.nextTick(shift)) 40 | } 41 | } 42 | 43 | // First time 44 | Vue.nextTick(() => { 45 | done = jobs[jobs.length - 1] 46 | if (done.toString().slice(0, 14) !== 'function (err)') { 47 | throw new Error('waitForUpdate chain is missing .then(done)') 48 | } 49 | shift() 50 | }) 51 | 52 | return chainer 53 | } 54 | 55 | exports.nextTick = nextTick 56 | exports.delay = time => new Promise(resolve => setTimeout(resolve, time)) 57 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // Polyfill fn.bind() for PhantomJS 2 | import bind from 'function-bind' 3 | /* eslint-disable no-extend-native */ 4 | Function.prototype.bind = bind 5 | 6 | // Polyfill Object.assign for PhantomJS 7 | import objectAssign from 'object-assign' 8 | Object.assign = objectAssign 9 | 10 | // require all src files for coverage. 11 | // you can also change this to match only the subset of files that 12 | // you want coverage for. 13 | const srcContext = require.context('../src', true, /^\.\/(?!index(\.js)?$)/) 14 | srcContext.keys().forEach(srcContext) 15 | 16 | // Use a div to insert elements 17 | before(function () { 18 | const el = document.createElement('DIV') 19 | el.id = 'tests' 20 | document.body.appendChild(el) 21 | }) 22 | 23 | // Remove every test html scenario 24 | afterEach(function () { 25 | const el = document.getElementById('tests') 26 | for (let i = 0; i < el.children.length; ++i) { 27 | el.removeChild(el.children[i]) 28 | } 29 | }) 30 | 31 | const specsContext = require.context('./specs', true) 32 | specsContext.keys().forEach(specsContext) 33 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const baseConfig = require('../build/webpack.config.dev.js') 3 | 4 | const webpackConfig = merge(baseConfig, { 5 | // use inline sourcemap for karma-sourcemap-loader 6 | devtool: '#inline-source-map' 7 | }) 8 | 9 | webpackConfig.plugins = [] 10 | 11 | const vueRule = webpackConfig.module.rules.find(rule => rule.loader === 'vue-loader') 12 | vueRule.options = vueRule.options || {} 13 | vueRule.options.loaders = vueRule.options.loaders || {} 14 | vueRule.options.loaders.js = 'babel-loader' 15 | 16 | // no need for app entry during tests 17 | delete webpackConfig.entry 18 | 19 | module.exports = function (config) { 20 | config.set({ 21 | // to run in additional browsers: 22 | // 1. install corresponding karma launcher 23 | // http://karma-runner.github.io/0.13/config/browsers.html 24 | // 2. add it to the `browsers` array below. 25 | browsers: ['Chrome'], 26 | frameworks: ['mocha', 'chai-dom', 'sinon-chai'], 27 | reporters: ['spec', 'coverage'], 28 | files: ['./index.js'], 29 | preprocessors: { 30 | './index.js': ['webpack', 'sourcemap'] 31 | }, 32 | webpack: webpackConfig, 33 | webpackMiddleware: { 34 | noInfo: true 35 | }, 36 | coverageReporter: { 37 | dir: './coverage', 38 | reporters: [ 39 | { type: 'lcov', subdir: '.' }, 40 | { type: 'text-summary' } 41 | ] 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /test/visual.js: -------------------------------------------------------------------------------- 1 | import 'style-loader!css-loader!mocha-css' 2 | 3 | // create a div where mocha can add its stuff 4 | const mochaDiv = document.createElement('DIV') 5 | mochaDiv.id = 'mocha' 6 | document.body.appendChild(mochaDiv) 7 | 8 | import 'mocha/mocha.js' 9 | import sinon from 'sinon' 10 | import chai from 'chai' 11 | window.mocha.setup({ 12 | ui: 'bdd', 13 | slow: 750, 14 | timeout: 5000, 15 | globals: [ 16 | '__VUE_DEVTOOLS_INSTANCE_MAP__', 17 | 'script', 18 | 'inject', 19 | 'originalOpenFunction' 20 | ] 21 | }) 22 | window.sinon = sinon 23 | chai.use(require('chai-dom')) 24 | chai.use(require('sinon-chai')) 25 | chai.should() 26 | 27 | let vms = [] 28 | let testId = 0 29 | 30 | beforeEach(function () { 31 | this.DOMElement = document.createElement('DIV') 32 | this.DOMElement.id = `test-${++testId}` 33 | document.body.appendChild(this.DOMElement) 34 | }) 35 | 36 | afterEach(function () { 37 | const testReportElements = document.getElementsByClassName('test') 38 | const lastReportElement = testReportElements[testReportElements.length - 1] 39 | 40 | if (!lastReportElement) return 41 | const el = document.getElementById(this.DOMElement.id) 42 | if (el) lastReportElement.appendChild(el) 43 | // Save the vm to hide it later 44 | if (this.DOMElement.vm) vms.push(this.DOMElement.vm) 45 | }) 46 | 47 | // Hide all tests at the end to prevent some weird bugs 48 | before(function () { 49 | vms = [] 50 | testId = 0 51 | }) 52 | after(function () { 53 | requestAnimationFrame(function () { 54 | setTimeout(function () { 55 | vms.forEach(vm => { 56 | // Hide if test passed 57 | if (!vm.$el.parentElement.classList.contains('fail')) { 58 | vm.$children[0].visible = false 59 | } 60 | }) 61 | }, 100) 62 | }) 63 | }) 64 | 65 | const specsContext = require.context('./specs', true) 66 | specsContext.keys().forEach(specsContext) 67 | 68 | window.mocha.checkLeaks() 69 | window.mocha.run() 70 | --------------------------------------------------------------------------------