Github address: github.com/Lstmxx/axio…

preface

Some time ago when I was doing optimization in the company, I saw that many mobile terminal projects and desktop projects I wrote before used the insensitive refresh token, so I simply packaged this into an NPM package to use.

Refresh token without feeling

The refreshing token principle is quite simple, in fact, when the interface returns 401, apply for a new token at the back end.

Implementation approach

Check if 401 is returned in the Rejected response interceptor of AXIos. If so, refresh the token and send the request again.

  • The encapsulation token is saved to the sessionStorage.
// ./src/util/tokenHelp.js
const TOKEN_KEY = 'token'
const storage = window.sessionStorage
export function setToken (token, type) {
  storage.setItem(TOKEN_KEY, token)
  storage.setItem('type', type)
}

export function getToken () {
  const token = storage.getItem(TOKEN_KEY)
  if (token) return token
  else return false
}

export function getType () {
  const type = storage.getItem('type')
  return type
}
Copy the code
  • Encapsulation axios
// /src/core/axios.js
import axios from 'axios'
import { merge } from '.. /util/util'
const baseConfig = {
  baseUrl: ' '.timeout: 6000.responseType: 'application/json'.withCredentials: true.headers: {
    get: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
    },
    post: {
      'Content-Type': 'application/json; charset=utf-8'}},transformRequest: [function (data) {
    data = JSON.stringify(data)
    return data
  }],
  transformResponse: [function (data) {
    // Perform any conversion on data
    if (typeof data === 'string' && data.startsWith('{')) {
      data = JSON.parse(data)
    }
    return data
  }]
}
export function initAxios (config) {
  const _config = merge(baseConfig, config)
  const _axios = axios.create(_config)
  return _axios
}
Copy the code

The merge function is written in reference to axios’s merge, primarily to merge config. Of course, object. assign is not impossible, but after all, the purpose is to package NPM, compatibility should be checked.

  • The wrapper throws the Service object. Notice here that I have added async to the error receiving function of response. With await, I can write synchronous code without embedding both then and catch.
// ./src/index.js
import { setToken, getToken } from './util/tokenHelp'
import { isFunction } from './util/util'
import { initAxios } from './core/axios'

const Service = function Service (options) {
  this.isReflash = false
  this.reTryReqeustList = []
  const config = options.config ? options.config : {}
  const responseInterceptors = options.responseInterceptors
  const requestInterceptors = options.requestInterceptors
  const reflashTokenConfig = options.refleshTokenConfig || null
  const getTokenFn = options.getTokenFn
  const axios = initAxios(config)
  function _refleshToken () {
    return new Promise((resolve, reject) = > {
      axios.request(reflashTokenConfig).then((response) = > {
        const isResolve = getTokenFn(response, setToken)
        if (isResolve) resolve()
        else reject(new Error('get token error'))
      }).catch((err) = > {
        reject(err)
      })
    })
  }
  axios.interceptors.request.use((_config) = > {
    const c = isFunction(requestInterceptors) ? requestInterceptors(_config, getToken) : _config
    return c
  }, (error) = > {
    error.data = {}
    error.data.msg = 'Server exception, please contact administrator! '
    return Promise.resolve(error)
  })
  axios.interceptors.response.use((response) = > {
    if (isFunction(responseInterceptors)) {
      return responseInterceptors(response)
    }
    return response
  }, async (error) => {
    if (error.response && error.response.status === 401) {
      try {
        refleshTokenConfig && await _refleshToken()
        return axios.request(error.response.config)
      } catch (err) {
        return Promise.reject(err)
      }
    }
    return Promise.reject(error)
  })
  this.axios = axios
}

Service.prototype.request = function request (options) {
  return this.axios.request(options)
}

export default Service
Copy the code

After the package is complete, introduce to the business layer call test to see first ~

import Service from './axios-reflash-token/src/index.js'
const showStatus = (status) = > {
  let message = ' '
  // This block of code can be optimized using the policy pattern
  switch (status) {
    case 400:
      message = 'Request error (400)'
      break
    case 401:
      message = 'Not authorized, please log back in (401)'
      break
    case 403:
      message = 'Access denied (403)'
      break
    case 404:
      message = 'Request error (404)'
      break
    case 408:
      message = 'Request timed out (408)'
      break
    case 500:
      message = 'Server error (500)'
      break
    case 501:
      message = 'Service Not realized (501)'
      break
    case 502:
      message = 'Network Error (502)'
      break
    case 503:
      message = 'Service unavailable (503)'
      break
    case 504:
      message = 'Network Timeout (504)'
      break
    case 505:
      message = 'HTTP version not supported (505)'
      break
    case 1001:
      message = 'Verification failed'
      break
    default:
      message = 'Connection error (${status})! `
  }
  return `${message}Please check the network or contact the administrator! `
}
const options = {
  config: {
    baseURL: 'http://test.platform.xbei.pro/api'.timeout: 6000
  },
  requestInterceptors: function (config, getToken) {
    config.headers.Authorization = 'Bearer ' + getToken()
    return config
  },
  responseInterceptors: function (response) {
    const data = response.data
    const res = {
      status: data.code || response.status,
      data,
      msg: ' '
    }
    res.msg = data.message || showStatus(res.status)
    return res
  },
  refreshTokenConfig: {
    url: '/platform/login'.method: 'POST'.data: {}},getTokenFn: function (response, setToken) {
    if (response.data.code === 200) {
      setToken(response.data.data.token, response.data.data.tokenType)
      return true
    }
    return false}}const service = new Service(options)
...
Copy the code

And as expected, it was done… Wait, what happens when you have multiple requests at the same time

Oh, this is not ok. I switched token acquisition several times. This is because in the case of multiple requests, each request is asynchronous. Before the token is set, reflashToken will be invoked several times to refresh the token.

The multiple token refresh problem is resolved

Actually solution is also very simple, because axios. Interceptors. Whether it is fulfilled in the response or is rejected is returned Promise (axios request inside chain calls), However, after the Promise enters pending, it will wait for the resolve or reject until it reaches the depressing or Rejected. Therefore, as long as we save the corresponding resolve with the array when we return the Promise, Then wait until the token is refreshed and then execute the requests in the array one by one.

  • To improve the
const Service = function Service (options) {
  this.isReflesh = false
  this.reTryReqeustList = []
  ...
  axios.interceptors.response.use((response) = > {
    if (isFunction(responseInterceptors)) {
      return responseInterceptors(response)
    }
    return response
  }, async (error) => {
    if (error.response && error.response.status === 401) {
      if (!this.isReflash) {
        this.isReflash = true
        try {
          reflashTokenConfig && await _reflashToken()
          this.isReflash = false
          while (this.reTryReqeustList.length > 0) {
            const cb = this.reTryReqeustList.shift()
            cb()
          }
          return axios.request(error.response.config)
        } catch (err) {
          return Promise.reject(err)
        }
      } else {
        return new Promise((resolve) = > {
          this.reTryReqeustList.push(
            () = > resolve(axios.request(error.response.config))
          )
        })
      }
    }
    return Promise.reject(error)
  })
  ...
}
Copy the code

Let’s test it out again

OK! No problem, then the next step is to pack into an NPM package.

packaging

The package is initialized using rollup

mkdir axios-refresh-token
cd axios-refresh-token
npm init
Copy the code

Modify the package. The json

"main": "dist/axios-refresh-token.cjs.js", "module": "dist/axios-refresh-token.esm.js", "browser": "dist/axios-refresh-token.umd.js", ... "Dependencies" : {" axios ":" ^ 0.21.0 "}, "devDependencies" : {" @ Babel/core ": "^ 7.0.0 @", "Babel/plugin - transform - runtime" : "^ 7.12.1", "@ Babel/preset - env" : "^ 7.12.1", "@ rollup/plugin - Babel" : "^ 5.2.1," "@ rollup/plugin - commonjs" : "^ 16.0.0", "@ rollup/plugin - json" : "^ 4.1.0", "@ rollup/plugin - node - resolve" : "^ 10.0.0," "@ types/babel__core" : "^ 7.1.9", "a rollup" : "^ 2.0.0", "a rollup - plugin - terser" : "^ 7.0.2"}, "files" : [ "dist", "src" ] ...Copy the code

After modification, NPM install installs dependencies. This package uses five plug-ins.

  • Rollup /plugin-json Converts JSON files to ES6 modules
  • Rollup /plugin-node-resolve resolves node_modules files
  • Rollup /plugin-commonjs Converts commonJS modules to ES6 modules for rollup to handle
  • @ rollup/plugin – Babel integrated Babel
  • Rollup-plugin-terser compression code

Create a new rollup.cofnig.js file in the project root directory.

import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser'

// const isDev = process.env.NODE_ENV ! == 'production'

export default {
  input: 'src/index.js'.output: [{file: 'dist/axios-reflash-token.cjs.js'.format: 'cjs'.name: 'cjs'
    },
    {
      file: 'dist/axios-reflash-token.es.js'.format: 'es'.name: 'es'
    },
    {
      file: 'dist/axios-reflash-token.umd.js'.format: 'umd'.name: 'umd'}].plugins: [
    json(),
    nodeResolve(),
    commonjs(),
    babel({
      babelHelpers: 'runtime'.exclude: 'node_modules/**'.plugins: [
        '@babel/plugin-transform-runtime'
      ]
    }),
    terser()
  ]
}
Copy the code

Packaging –

rollup -c ./rollup.config.js
Copy the code

If you want to package to NPM, you also need to use the NPM adduser command to add users. After that, you can upload it.

NPM publish --access public // Publish --access public if @xxx/ is includedCopy the code

Once packed, you can install the introduction

NPM install @lstmxx/axios-refresh-token --save import Service from '@lstmxx/axios-refresh-token'Copy the code

Refer to the connection

  • [1] developer.mozilla.org/zh-CN/docs/…
  • [2] www.axios-js.com/
  • [3] rollupjs.org/guide/en/#i…