├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── jest.config.js ├── lib ├── index.ts └── utils │ └── index.ts ├── package-lock.json ├── package.json ├── rollup.config.js ├── test ├── index.spec.js └── utils.spec.js ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Use 4 spaces for the Python files 13 | [*.py] 14 | indent_size = 4 15 | max_line_length = 80 16 | 17 | # The JSON files contain newlines inconsistently 18 | [*.json] 19 | insert_final_newline = ignore 20 | 21 | # Minified JavaScript files shouldn't be changed 22 | [**.min.js] 23 | indent_style = ignore 24 | insert_final_newline = ignore 25 | 26 | # Makefiles always use tabs for indentation 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # Batch files use tabs for indentation 31 | [*.bat] 32 | indent_style = tab 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | name: Test 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | test-core-package: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [12.x] 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Test core package 25 | run: npm ci 26 | 27 | - name: Run test 28 | run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | #IDEs. 47 | .idea/ 48 | .vscode/ 49 | 50 | # Dist. 51 | dist/ 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 LancerComet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue-jsonp 2 | 3 | [![npm version](https://badge.fury.io/js/vue-jsonp.svg)](https://badge.fury.io/js/vue-jsonp) 4 | [![VueJsonp](https://github.com/LancerComet/vue-jsonp/workflows/Test/badge.svg)](https://github.com/LancerComet/vue-jsonp/actions) 5 | 6 | A tiny library for handling JSONP request. 7 | 8 | ## Quick Start 9 | 10 | As Vue plugin: 11 | 12 | ```ts 13 | import { VueJsonp } from 'vue-jsonp' 14 | 15 | // Vue Plugin. 16 | Vue.use(VueJsonp) 17 | 18 | // Now you can use "$jsonp" on Vue components. 19 | const vm = new Vue() 20 | const data = await vm.$jsonp('/some-jsonp-url', { 21 | myCustomUrlParam: 'veryNice' 22 | }) 23 | ``` 24 | 25 | Use function directly: 26 | 27 | ```ts 28 | import { jsonp } from 'vue-jsonp' 29 | 30 | // Pass a response type. 31 | const data = await jsonp('/some-jsonp-url', { 32 | myCustomUrlParam: 'veryNice' 33 | }) 34 | ``` 35 | 36 | ## Send data and set query & function name 37 | 38 | ### Send data 39 | 40 | ```ts 41 | // The request url will be "/some-jsonp-url?type=admin&date=2020&callback=jsonp_{RANDOM_STR}". 42 | const data = await jsonp('/some-jsonp-url', { 43 | type: 'admin', 44 | date: 2020 45 | }) 46 | ``` 47 | 48 | ### Custom query & function name 49 | 50 | The url uniform is `/url?{callbackQuery}={callbackName}&...` and the default is `/url?callback=jsonp_{RANDOM_STRING}&...`. 51 | 52 | And you can change it like this: 53 | 54 | ```ts 55 | // The request url will be "/some-jsonp-url?type=admin&date=2020&cb=jsonp_func". 56 | jsonp('/some-jsonp-url', { 57 | callbackQuery: 'cb', 58 | callbackName: 'jsonp_func', 59 | type: 'admin', 60 | date: 2020 61 | }) 62 | ``` 63 | 64 | ## Module exports 65 | 66 | - `VueJsonp: PluginObject` 67 | 68 | - `jsonp: (url: string, param?: IJsonpParam, timeout?: number) => Promise` 69 | 70 | ## API 71 | 72 | ### IJsonpParam 73 | 74 | IJsonpParam is the type of param for jsonp function. 75 | 76 | ```ts 77 | /** 78 | * JSONP parameter declaration. 79 | */ 80 | interface IJsonpParam { 81 | /** 82 | * Callback query name. 83 | * This param is used to define the query name of the callback function. 84 | * 85 | * @example 86 | * // The request url will be "/some-url?myCallback=jsonp_func&myCustomUrlParam=veryNice" 87 | * const result = await jsonp('/some-url', { 88 | * callbackQuery: 'myCallback', 89 | * callbackName: 'jsonp_func', 90 | * myCustomUrlParam: 'veryNice' 91 | * }) 92 | * 93 | * @default callback 94 | */ 95 | callbackQuery?: string 96 | 97 | /** 98 | * Callback function name. 99 | * This param is used to define the jsonp function name. 100 | * 101 | * @example 102 | * // The request url will be "/some-url?myCallback=jsonp_func&myCustomUrlParam=veryNice" 103 | * const result = await jsonp('/some-url', { 104 | * callbackQuery: 'myCallback', 105 | * callbackName: 'jsonp_func', 106 | * myCustomUrlParam: 'veryNice' 107 | * }) 108 | * 109 | * @default jsonp_ + randomStr() 110 | */ 111 | callbackName?: string 112 | 113 | /** 114 | * Custom data. 115 | */ 116 | [key: string]: any 117 | } 118 | ``` 119 | 120 | ## Example 121 | 122 | ```ts 123 | import Vue from 'vue' 124 | import { VueJsonp } from 'vue-jsonp' 125 | 126 | Vue.use(VueJsonp) 127 | 128 | const vm = new Vue() 129 | const { code, data, message } = await vm.$jsonp<{ 130 | code: number, 131 | message: string, 132 | data: { 133 | id: number, 134 | nickname: string 135 | } 136 | }>('/my-awesome-url', { 137 | name: 'MyName', age: 20 138 | }) 139 | 140 | assert(code === 0) 141 | assert(message === 'ok') 142 | assert(data.id === 1) 143 | assert(data.nickname === 'John Smith') 144 | ``` 145 | 146 | ```ts 147 | import { jsonp } from 'vue-jsonp' 148 | 149 | const result = await jsonp('/my-awesome-url') 150 | assert(result === 'such a jsonp') 151 | ``` 152 | 153 | ## License 154 | 155 | MIT 156 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { jsWithBabel: tsjPreset } = require('ts-jest/presets') 2 | 3 | module.exports = { 4 | preset: 'jest-puppeteer', 5 | testMatch: ["**/?(*.)+(spec|test).(j|t)s"], 6 | testTimeout: 10000, 7 | transform: { 8 | ...tsjPreset.transform 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vue Jsonp. 3 | * # Carry Your World # 4 | * 5 | * @author: LancerComet 6 | * @license: MIT 7 | */ 8 | 9 | import Vue from 'vue' 10 | import { PluginObject } from 'vue/types/plugin' 11 | import { flatten, formatParams, randomStr } from './utils' 12 | 13 | const DEFAULT_TIMEOUT: number = 5000 14 | 15 | declare module 'vue/types/vue' { 16 | // tslint:disable-next-line:interface-name 17 | interface Vue { 18 | $jsonp: typeof jsonp 19 | } 20 | } 21 | 22 | /** 23 | * JSONP Vue plugin. 24 | * 25 | * @example 26 | * Vue.use(VueJsonp) 27 | */ 28 | // tslint:disable-next-line:variable-name 29 | const VueJsonp: PluginObject = { 30 | install (V: typeof Vue) { 31 | V.prototype.$jsonp = jsonp 32 | } 33 | } 34 | 35 | /** 36 | * Make a json request. 37 | * 38 | * @template T 39 | * @param {string} url Target URL address. 40 | * @param {IJsonpParam} [param={}] Querying params object. 41 | * @param {number} [timeout] Timeout setting (ms). 42 | * @returns {Promise} 43 | * 44 | * @example 45 | * const data = await jsonp('/url', { 46 | * type: 'admin', 47 | * date: '2020' 48 | * }) 49 | */ 50 | function jsonp (url: string, param?: IJsonpParam, timeout?: number): Promise 51 | function jsonp (url: string, param?: IJsonpParam, config?: IConfig): Promise 52 | function jsonp ( 53 | url: string, 54 | param: IJsonpParam = {}, 55 | config?: undefined | number | IConfig 56 | ): Promise { 57 | if (typeof url !== 'string') { 58 | throw new Error('[Vue-jsonp] Type of param "url" is not string.') 59 | } 60 | 61 | if (typeof param !== 'object' || !param) { 62 | throw new Error('[Vue-jsonp] Invalid params, should be an object.') 63 | } 64 | 65 | const timeout = typeof config === 'number' 66 | ? config 67 | : config?.timeout ?? DEFAULT_TIMEOUT 68 | 69 | let arrayIndicator = '[]' 70 | if (typeof config === 'object') { 71 | const _indicator = config.arrayIndicator 72 | if (typeof _indicator === 'string') { 73 | arrayIndicator = _indicator 74 | } 75 | } 76 | 77 | return new Promise((resolve, reject) => { 78 | const callbackQuery = typeof param.callbackQuery === 'string' 79 | ? param.callbackQuery 80 | : 'callback' 81 | const callbackName = typeof param.callbackName === 'string' 82 | ? param.callbackName 83 | : 'jsonp_' + randomStr() 84 | 85 | param[callbackQuery] = callbackName 86 | 87 | // Remove callbackQuery and callbackName. 88 | delete param.callbackQuery 89 | delete param.callbackName 90 | 91 | // Convert params to querying str. 92 | let queryStrs: (string[])[] = [] 93 | Object.keys(param).forEach(queryKey => { 94 | queryStrs = queryStrs.concat(formatParams(queryKey, param[queryKey], arrayIndicator)) 95 | }) 96 | 97 | const queryStr = flatten(queryStrs).join('&') 98 | 99 | const onError = () => { 100 | removeErrorListener() 101 | clearTimeout(timeoutTimer) 102 | reject({ 103 | status: 400, 104 | statusText: 'Bad Request' 105 | }) 106 | } 107 | 108 | const removeErrorListener = () => { 109 | paddingScript.removeEventListener('error', onError) 110 | } 111 | 112 | const removeScript = () => { 113 | document.body.removeChild(paddingScript) 114 | delete window[callbackName] 115 | } 116 | 117 | // Timeout timer. 118 | let timeoutTimer = null 119 | 120 | // Setup timeout. 121 | if (timeout > -1) { 122 | timeoutTimer = setTimeout(() => { 123 | removeErrorListener() 124 | removeScript() 125 | reject({ 126 | statusText: 'Request Timeout', 127 | status: 408 128 | }) 129 | }, timeout) 130 | } 131 | 132 | // Create global function. 133 | window[callbackName] = (json: T) => { 134 | clearTimeout(timeoutTimer) 135 | removeErrorListener() 136 | removeScript() 137 | resolve(json) 138 | } 139 | 140 | // Create script element. 141 | const paddingScript = document.createElement('script') 142 | 143 | // Add error listener. 144 | paddingScript.addEventListener('error', onError) 145 | 146 | // Append to head element. 147 | paddingScript.src = url + (/\?/.test(url) ? '&' : '?') + queryStr 148 | document.body.appendChild(paddingScript) 149 | }) 150 | } 151 | 152 | export { 153 | VueJsonp, 154 | jsonp 155 | } 156 | 157 | /** 158 | * JSONP parameter declaration. 159 | */ 160 | interface IJsonpParam { 161 | /** 162 | * Callback query name. 163 | * This param is used to define the query name of the callback function. 164 | * 165 | * @example 166 | * // The request url will be "/some-url?myCallback=jsonp_func&myCustomUrlParam=veryNice" 167 | * const result = await jsonp('/some-url', { 168 | * callbackQuery: 'myCallback', 169 | * callbackName: 'jsonp_func', 170 | * myCustomUrlParam: 'veryNice' 171 | * }) 172 | * 173 | * @default callback 174 | */ 175 | callbackQuery?: string 176 | 177 | /** 178 | * Callback function name. 179 | * This param is used to define the jsonp function name. 180 | * 181 | * @example 182 | * // The request url will be "/some-url?myCallback=jsonp_func&myCustomUrlParam=veryNice" 183 | * const result = await jsonp('/some-url', { 184 | * callbackQuery: 'myCallback', 185 | * callbackName: 'jsonp_func', 186 | * myCustomUrlParam: 'veryNice' 187 | * }) 188 | * 189 | * @default jsonp_ + randomStr() 190 | */ 191 | callbackName?: string 192 | 193 | /** 194 | * Custom data. 195 | */ 196 | [key: string]: any 197 | } 198 | 199 | /** 200 | * JSONP Config. 201 | */ 202 | interface IConfig { 203 | /** 204 | * Request timeout, ms. 205 | * 206 | * @default 5000 207 | */ 208 | timeout?: number 209 | 210 | /** 211 | * This is the indicator that used in query string to indicate arrays. 212 | * 213 | * @example 214 | * // When you pass a '[]' or nothing: 215 | * a[]=1&a[]=2&a[]=3 // This form is used widely. 216 | * 217 | * // An empty sring was passed: 218 | * a=1&a=2&a=3 // This is a custom example. 219 | * 220 | * @default '[]' 221 | */ 222 | arrayIndicator?: string 223 | } 224 | -------------------------------------------------------------------------------- /lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate random string. 3 | * 4 | * @return { string } 5 | */ 6 | function randomStr () { 7 | return (Math.floor(Math.random() * 100000) * Date.now()).toString(16) 8 | } 9 | 10 | /** 11 | * Format params into querying string. 12 | * 13 | * @return {string[]} 14 | */ 15 | function formatParams (queryKey: string, value: any, arrayIndicator: string = '[]'): string[] { 16 | queryKey = queryKey.replace(/=/g, '') 17 | let result: string[] = [] 18 | 19 | if (value === null || typeof value === 'undefined') { 20 | return result 21 | } 22 | 23 | switch (value.constructor) { 24 | case String: 25 | case Number: 26 | case Boolean: 27 | result.push(encodeURIComponent(queryKey) + '=' + encodeURIComponent(value)) 28 | break 29 | 30 | case Array: 31 | value.forEach(function (item) { 32 | result = result.concat(formatParams(`${queryKey}${arrayIndicator}=`, item, arrayIndicator)) 33 | }) 34 | break 35 | 36 | case Object: 37 | Object.keys(value).forEach(function (key) { 38 | const item = value[key] 39 | result = result.concat(formatParams(queryKey + '[' + key + ']', item, arrayIndicator)) 40 | }) 41 | break 42 | } 43 | 44 | return result 45 | } 46 | 47 | /** 48 | * Flat querys. 49 | * 50 | * @param {string[] | (string[])[]} array 51 | * @returns 52 | */ 53 | function flatten (array: string[] | (string[])[]): string[] { 54 | let querys = [] 55 | array.forEach(item => { 56 | if (typeof item === 'string') { 57 | querys.push(item) 58 | } else { 59 | querys = querys.concat(flatten(item)) 60 | } 61 | }) 62 | return querys 63 | } 64 | 65 | export { 66 | formatParams, 67 | flatten, 68 | randomStr 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-jsonp", 3 | "version": "2.1.0", 4 | "description": "A tiny library for handling JSONP request.", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.esm.js", 7 | "keywords": [ 8 | "Vue", 9 | "JSONP" 10 | ], 11 | "files": [ 12 | "dist/", 13 | "index.d.ts", 14 | "README.md" 15 | ], 16 | "scripts": { 17 | "build": "rollup -c", 18 | "test": "jest", 19 | "pretest": "npm run build", 20 | "preversion": "npm run test", 21 | "prepublish": "npm run test" 22 | }, 23 | "author": { 24 | "name": "LancerComet", 25 | "email": "chw644@hotmail.com" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/LancerComet/vue-jsonp.git" 30 | }, 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@types/expect-puppeteer": "^4.4.3", 34 | "@types/jest": "^26.0.14", 35 | "@types/jest-environment-puppeteer": "^4.4.0", 36 | "@types/puppeteer": "^3.0.2", 37 | "jest": "^26.4.2", 38 | "jest-puppeteer": "^4.4.0", 39 | "puppeteer": "^5.3.1", 40 | "rollup": "^2.28.2", 41 | "rollup-plugin-cleanup": "^3.2.1", 42 | "rollup-plugin-delete": "^2.0.0", 43 | "rollup-plugin-terser": "^7.0.2", 44 | "rollup-plugin-typescript2": "^0.27.3", 45 | "ts-jest": "^26.4.1", 46 | "tslint": "^6.1.3", 47 | "typescript": "^4.0.3", 48 | "vue": "^2.6.12" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2' 2 | import cleanup from 'rollup-plugin-cleanup' 3 | import del from 'rollup-plugin-delete' 4 | import { terser } from 'rollup-plugin-terser' 5 | 6 | export default { 7 | input: './lib/index.ts', 8 | 9 | output: [ 10 | { 11 | file: './dist/index.js', 12 | format: 'umd', 13 | name: 'VueJsonp' 14 | }, 15 | { 16 | file: './dist/index.esm.js', 17 | format: 'es' 18 | } 19 | ], 20 | 21 | plugins: [ 22 | typescript({ 23 | tsconfigOverride: { 24 | compilerOptions: { 25 | target: 'ES5' 26 | }, 27 | include: [ 28 | 'lib/**/*' 29 | ] 30 | } 31 | }), 32 | 33 | del({ 34 | targets: 'dist/*' 35 | }), 36 | 37 | terser(), 38 | cleanup() 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | require('expect-puppeteer') 2 | const path = require('path') 3 | 4 | describe('VueJsonp testing.', () => { 5 | it('Make a jsonp request.', async () => { 6 | await page.addScriptTag({ 7 | path: path.resolve(__dirname, '../dist/index.js') 8 | }) 9 | 10 | await page.addScriptTag({ 11 | path: path.resolve(__dirname, '../node_modules/vue/dist/vue.min.js') 12 | }) 13 | 14 | const result = await page.evaluate(async () => { 15 | Vue.use(VueJsonp.VueJsonp) 16 | 17 | const vm = new Vue() 18 | const isPluginInstalled = typeof vm.$jsonp === 'function' 19 | 20 | const jsonpResult = await VueJsonp.jsonp('https://static.lancercomet.com/lancercomet/misc/vue-jsonp-test-01.js', { 21 | callbackQuery: 'callback', 22 | callbackName: 'jsonp_callback' 23 | }) 24 | 25 | return { 26 | isPluginInstalled, 27 | jsonpResult 28 | } 29 | }) 30 | 31 | expect(result.isPluginInstalled).toBe(true) 32 | expect(result.jsonpResult).toEqual({ 33 | data: { 34 | name: 'John Smith', 35 | age: 100 36 | }, 37 | code: 0, 38 | message: 'OK' 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | require('expect-puppeteer') 2 | const { flatten, formatParams } = require('../lib/utils') 3 | 4 | describe('Utils test.', () => { 5 | it('Should format params correctly - simple.', () => { 6 | const dateNow = Date.now() 7 | const result = createQueryStr({ 8 | callbackQuery: 'callback', 9 | callbackName: 'func', 10 | date: dateNow 11 | }) 12 | const expected = 'callbackQuery=callback&callbackName=func&date=' + dateNow 13 | expect(decodeURIComponent(result)).toBe(expected) 14 | }) 15 | 16 | it('Should format params correctly - complex.', () => { 17 | const result = createQueryStr({ 18 | callbackQuery: 'callback', 19 | callbackName: 'func', 20 | source: 'testing', 21 | info: { 22 | name: 'LancerComet', 23 | age: 27, 24 | address: ['Beijing', 'NewYork'], 25 | score: { 26 | math: 10, 27 | english: 100 28 | }, 29 | something: { 30 | one: [1, 2, { wow: true }], 31 | two: { 32 | wow: false, 33 | three: [1, 2, 3] 34 | } 35 | } 36 | } 37 | }) 38 | const expected = 'callbackQuery=callback&callbackName=func&source=testing&info[name]=LancerComet&info[age]=27&info[address][]=Beijing&info[address][]=NewYork&info[score][math]=10&info[score][english]=100&info[something][one][]=1&info[something][one][]=2&info[something][one][][wow]=true&info[something][two][wow]=false&info[something][two][three][]=1&info[something][two][three][]=2&info[something][two][three][]=3' 39 | expect(decodeURIComponent(result)).toBe(expected) 40 | }) 41 | 42 | it('Should format params correctly - from issue.', () => { 43 | const self = { 44 | email: 'mail@hotmail.com', 45 | first_name: 'FirstName', 46 | last_name: 'LastName' 47 | } 48 | const result = createQueryStr({ 49 | EMAIL: self.email, 50 | FNAME: self.first_name, 51 | LNAME: self.last_name 52 | }) 53 | 54 | const expected = 'EMAIL=' + self.email + '&FNAME=' + self.first_name + '&LNAME=' + self.last_name 55 | expect(decodeURIComponent(result)).toBe(expected) 56 | }) 57 | 58 | it('Array separator should be configurable.', () => { 59 | const result = createQueryStr({ 60 | a: 'a', 61 | b: ['1', '2', '3'], 62 | c: { 63 | d: 'd', 64 | e: [1, 2, { wow: true }] 65 | } 66 | }, '') 67 | const expected = 'a=a&b=1&b=2&b=3&c[d]=d&c[e]=1&c[e]=2&c[e][wow]=true' 68 | expect(decodeURIComponent(result)).toBe(expected) 69 | }) 70 | 71 | // Fix for #41. 72 | it('It should get correct params when undefined or null was got.', () => { 73 | const params = { 74 | a: undefined, 75 | b: null, 76 | c: 'c', 77 | d: 1 78 | } 79 | const result = createQueryStr(params) 80 | const expected = 'c=c&d=1' 81 | expect(decodeURIComponent(result)).toBe(expected) 82 | }) 83 | }) 84 | 85 | function createQueryStr (param, arraySeprator) { 86 | const querys = [] 87 | Object.keys(param).forEach(keyName => { 88 | querys.push(formatParams(keyName, param[keyName], arraySeprator)) 89 | }) 90 | return flatten(querys).join('&') 91 | } 92 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "moduleResolution": "node", 5 | "module": "ESNext", 6 | "target": "ES2018", 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "lib": [ 10 | "dom", 11 | "ES5", 12 | "ES2015", 13 | "ES2016", 14 | "ES2017", 15 | "ES2018" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "rules": { 7 | "array-type": [ 8 | true, 9 | "array" 10 | ], 11 | "arrow-parens": [ 12 | true, 13 | "ban-single-arg-parens" 14 | ], 15 | "arrow-return-shorthand": true, 16 | "ban-comma-operator": true, 17 | "ban-types": { 18 | "options": [ 19 | [ 20 | "Object", 21 | "Avoid using the `Object` type. Did you mean `object`?" 22 | ], 23 | [ 24 | "Function", 25 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." 26 | ], 27 | [ 28 | "Boolean", 29 | "Avoid using the `Boolean` type. Did you mean `boolean`?" 30 | ], 31 | [ 32 | "Number", 33 | "Avoid using the `Number` type. Did you mean `number`?" 34 | ], 35 | [ 36 | "String", 37 | "Avoid using the `String` type. Did you mean `string`?" 38 | ] 39 | ] 40 | }, 41 | "class-name": true, 42 | "comment-format": [true, "check-space"], 43 | "curly": true, 44 | "eofline": true, 45 | "forin": true, 46 | "indent": [true, "spaces", 2], 47 | "interface-name": [ 48 | true, 49 | "always-prefix" 50 | ], 51 | "interface-over-type-literal": true, 52 | "jsdoc-format": true, 53 | "label-position": true, 54 | "max-line-length": [true, 120], 55 | "max-classes-per-file": false, 56 | "member-access": [ 57 | true, 58 | "no-public" 59 | ], 60 | "member-ordering": false, 61 | "no-angle-bracket-type-assertion": true, 62 | "no-bitwise": false, 63 | "no-console": [true, 1], 64 | "no-consecutive-blank-lines": true, 65 | "no-debugger": false, 66 | "no-duplicate-switch-case": true, 67 | "no-empty-interface": true, 68 | "no-eval": true, 69 | "no-namespace": true, 70 | "no-string-literal": true, 71 | "no-internal-module": true, 72 | "no-reference": true, 73 | "no-shadowed-variable": true, 74 | "no-switch-case-fall-through": true, 75 | "no-trailing-whitespace": [ 76 | true, 77 | "ignore-template-strings" 78 | ], 79 | "no-unused-expression": [ 80 | true, 81 | "allow-fast-null-checks" 82 | ], 83 | "no-var-requires": true, 84 | "no-var-keyword": true, 85 | "object-literal-sort-keys": false, 86 | "object-literal-key-quotes": [ 87 | true, 88 | "consistent-as-needed" 89 | ], 90 | "object-literal-shorthand": true, 91 | "one-variable-per-declaration": [ 92 | true, "ignore-for-loop" 93 | ], 94 | "only-arrow-functions": false, 95 | "ordered-imports": true, 96 | "prefer-const": true, 97 | "quotemark": [ 98 | true, 99 | "single", 100 | "avoid-escape" 101 | ], 102 | "radix": false, 103 | "semicolon": [ 104 | true, 105 | "never" 106 | ], 107 | "space-before-function-paren": true, 108 | "triple-equals": true, 109 | "typedef-whitespace": [ 110 | true, 111 | { 112 | "call-signature": "nospace", 113 | "index-signature": "nospace", 114 | "parameter": "nospace", 115 | "property-declaration": "nospace", 116 | "variable-declaration": "nospace" 117 | }, 118 | { 119 | "call-signature": "onespace", 120 | "index-signature": "onespace", 121 | "parameter": "onespace", 122 | "property-declaration": "onespace", 123 | "variable-declaration": "onespace" 124 | } 125 | ], 126 | "whitespace": [ 127 | true, 128 | "check-branch", "check-decl", 129 | "check-rest-spread", "check-type", "check-type-operator", 130 | "check-module", "check-operator", "check-separator", "check-preblock" 131 | ], 132 | "trailing-comma": [ 133 | true, 134 | { 135 | "multiline": "never", 136 | "singleline": "never" 137 | } 138 | ], 139 | "variable-name": [ 140 | true, 141 | "ban-keywords", 142 | "check-format", 143 | "allow-leading-underscore" 144 | ] 145 | } 146 | } 147 | --------------------------------------------------------------------------------