npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@zd~/request

v0.2.5

Published

A request library.

Downloads

378

Readme

@zd~/Request

A request library based on Axios. Support WEB/UNI-APP/WX-PROGRAM

example

import type { AxiosRequestConfig, Canceler } from 'axios'

import axios, { AxiosError } from 'axios'
import { saveAs } from 'file-saver'
import { isObject, merge } from 'lodash-es'
import type { HttpRequestConfig, ResponseResult } from '@zd~/request/http'
import {
  ContentTypeEnum,
  HttpRequest,
  RequestMethodsEnum as HttpRequestMethodsEnum,
} from '@zd~/request'
import { getTokenCookie, removeTokenCookie } from '~/utils/cookie'

export interface CustomConfig {
  /**
   * @description 是否需要token
   */
  withToken?: boolean
  /**
   * @description 忽略重复请求。第一个请求未完成时进行第二个请求,第一个会被被取消
   *              参考 axios 取消请求 https://axios-http.com/zh/docs/cancellation
   */
  ignoreRepeatRequest?: boolean
  /**
   * 下载文件名称
   */
  filename?: string
};

const tokenKey = 'Authorization'
const tokenKeyScheme = 'Bearer'
const cancelMap = new Map<string, Canceler>()

function getHeaderFileName(headers: Record<string, any>) {
  ['file-name', 'download-filename', 'File-Name', 'FileName', 'Filename'].forEach((key) => {
    if (Object.prototype.hasOwnProperty.call(headers, key)) {
      if (headers[key])
        return `${headers[key]}`
    }
  })
  return ''
}

export const request = new HttpRequest<CustomConfig>({
  baseURL: import.meta.env.VITE_APP_API_URL,
  timeout: 15 * 1000,
  headers: {
    'Content-Type': ContentTypeEnum.JSON,
  },
  getResponse: false,
  ignoreRepeatRequest: false,
  withToken: true,
  onUploadProgress(_progressEvent) {
    // 处理原生进度事件
  },
  // `onDownloadProgress` 允许为下载处理进度事件
  // 浏览器专属
  onDownloadProgress(progressEvent) {
    if (progressEvent.total) {
      const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100)
      console.log(`Download Progress: ${progress}%`)
    }
  },
}, {
  request(config) {
    /**
     * token
     */
    const token = getTokenCookie()
    if (config?.withToken && token) {
      config.headers![tokenKey] = `${tokenKeyScheme} ${token}`
    }
    /**
     * 忽略重复请求。第一个请求未完成时进行第二个请求,第一个会被被取消
     */
    if (config.ignoreRepeatRequest) {
      const key = generateKey({ ...config })
      const cancelToken = new axios.CancelToken(c => cancelInterceptor(key, c))
      config.cancelToken = cancelToken
    }
    /**
     * 添加时间戳到 get 请求
     */
    if (config.method?.toUpperCase() === HttpRequestMethodsEnum.GET) {
      config.params = { _t: `${Date.now()}`, ...config.params }
    }

    return config
  },

  requestError(e) {
    console.log(e)
  },

  async response(_response) {
    cancelMap.delete(generateKey(_response.config))
    const config = _response.config as HttpRequestConfig<CustomConfig>
    if (config.getResponse) {
      // 处理下载文件
      if (_response.config.responseType === 'blob') {
        const blob = _response.data as unknown as Blob
        const filename = config.filename
        if (_response.data && blob.type !== 'application/json') {
          if (_response.data?.type && !filename) {
            saveAs(_response.data as unknown as Blob)
          }
          else {
            const urlList = config.url?.split('/')
            const extList = config.url?.split('.')
            const urlFileName = urlList && urlList?.length >= 0 ? urlList[urlList?.length - 1] : ''
            const ext = extList && extList?.length >= 0 ? extList[extList?.length - 1] : ''
            const _filename = filename || getHeaderFileName(config.headers || {}) || urlFileName || `${Date.now()}.${ext}`
            saveAs(_response.data, decodeURI(decodeURI(_filename)))
          }
        }
        else {
          const resText = await blob.text()
          const rspObj = JSON.parse(resText)
          return handleError(rspObj.msg || getSystemErrorMessage(rspObj.code))
        }
      }

      return _response
    }
    const responseData = _response.data as ResponseResult<object>

    if (responseData.code === 200) {
      return responseData as any
    }
    /**
     * 登录过期
     */
    if (responseData.code === 401) {
      removeTokenCookie()
    }

    const msg = responseData.msg || getSystemErrorMessage(responseData.code)

    return handleError(msg)
  },

  responseError(error) {
    if (error.config)
      cancelMap.delete(generateKey(error.config))

    if (error instanceof AxiosError) {
      handleError(getAxiosErrorErrorMessage(error.code))
    }

    throw error
  },
})

export function removeAllPenddingRequest() {
  for (const [, value] of cancelMap) {
    value?.('remove all pendding request')
  }
}

function cancelInterceptor(key: string, canceler: Canceler) {
  if (cancelMap.has(key)) {
    cancelMap.get(key)?.('cancel repeat request')
  }
  cancelMap.set(key, canceler)
}

function generateKey(config: AxiosRequestConfig) {
  const { url, method, params = {}, data = {} } = config
  return `${url}-${method}-${JSON.stringify(method === 'get' ? params : data)}`
}

async function handleError(msg: string) {
  console.error(msg)
  // window.showError?.(new Error(msg))
}

function transformRequest(params?: object) {
  if (!isObject(params))
    return ''
  let result = ''
  for (const propName of Object.keys(params)) {
    const value = params[propName as keyof typeof params]
    const part = `${encodeURIComponent(propName)}=`
    if (value !== null && typeof value !== 'undefined') {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && typeof value[key] !== 'undefined') {
            const params = `${propName}[${key}]`
            const subPart = `${encodeURIComponent(params)}=`
            result += `${subPart + encodeURIComponent(value[key])}&`
          }
        }
      }
      else {
        result += `${part + encodeURIComponent(value)}&`
      }
    }
  }
  return result
}

export function download(config: Parameters<typeof request.request>[0]) {
  const downloadBaseConfig: typeof config = {
    transformRequest: [
      (params: object) => {
        return transformRequest(params)
      },
    ],
    responseType: 'blob',
    headers: {
      'Content-Type': ContentTypeEnum.FORM_URLENCODED,
    },
    getResponse: true,
  }
  return request.request(merge({ }, downloadBaseConfig, config))
}

function getAxiosErrorErrorMessage(code?: string): string {
  switch (code) {
    case 'ERR_BAD_OPTION_VALUE':
      return '选项设置了错误的值'
    case 'ERR_BAD_OPTION':
      return '无效的或不支持的选项'
    case 'ECONNABORTED':
      return '网络连接被中断,通常因为请求超时'
    case 'ETIMEDOUT':
      return '操作超时'
    case 'ERR_NETWORK':
      return '网络错误'
    case 'ERR_FR_TOO_MANY_REDIRECTS':
      return '请求被重定向了太多次,可能导致无限循环'
    case 'ERR_DEPRECATED':
      return '使用了已被废弃的函数或方法'
    case 'ERR_BAD_RESPONSE':
      return '从服务器接收到无效或错误的响应'
    case 'ERR_BAD_REQUEST':
      return '发送的请求格式错误或无效'
    case 'ERR_CANCELED':
      return '请求已经被取消'
    case 'ERR_NOT_SUPPORT':
      return '使用的某个功能或方法不被支持'
    case 'ERR_INVALID_URL':
      return '提供的URL无效'
    default:
      return '未知错误'
  }
}

function getSystemErrorMessage(status: number) {
  switch (status) {
    case 400:
      return '错误请求,服务器无法理解请求的格式'
    case 401:
      return '无效的会话,或者会话已过期,请重新登录。'
    case 403:
      return '当前操作没有权限'
    case 404:
      return '服务器无法根据客户端的请求找到资源'
    case 405:
      return '网络请求错误,请求方法未允许!'
    case 408:
      return '网络请求超时!'
    case 500:
      return '服务器内部错误,无法完成请求'
    case 502:
      return '网关错误'
    case 503:
      return '服务器目前无法使用(由于超载或停机维护)'
    case 504:
      return '网络超时!'
    case 505:
      return 'http版本不支持该请求!'
    default:
      return '未知错误'
  }
}