🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@complex-suite/request

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@complex-suite/request - npm Package Compare versions

Comparing version
2.0.15
to
2.0.16
+11
-0
history.md

@@ -10,2 +10,13 @@ <!--

### 2.0.16
- fix(types): 新增 `RequestConfigWithUrl<R,L>` 类型(url必填其余可选),`request`/`get`/`post`/`form`/`json` 五方法入参从 `Partial<RequestConfig>` 改为 `RequestConfigWithUrl`,编译时强制 url 必传
- fix(request): `_parseRequestConfig` 开头对 data 做 `deepCloneData` 深拷贝,隔离调用方原对象与内部修改(类型转换、jsonToForm 等)
- fix(request): `_handleTokenLogin` 复用 `isLogining` 合并并发登录,若 !this.isLogining 则赋值 `this.rule.login('token')`,所有等待者共享同一 Promise,完成后在 finally 清空,与 `_handleRequest` 中的 login/refresh 分支逻辑一致
- fix(types): `RuleInitOption.refresh` 从必填改为可选 `refresh?: refreshType`,与运行时 `if (this.rule.refresh && ...)` 判空逻辑一致
- refactor(rule): 移除 `Rule.ts` 中 `defaultFormatUrl` 函数、`formatUrl` 字段及赋值、`formatUrlType` 导入,移除 `RuleInitOption.formatUrl` 字段,这些为从未被读取的死代码
- fix(auth): `_handleTokenLogin` 和 `_handleRequest` 的 refresh/login 分支引入 `refreshCount`/`loginCount` 引用计数器(声明处 `= 0` 初始化以满足 `strictPropertyInitialization`),创建共享 Promise 时重置为 0,加入时递增,重试 `.finally()` 和共享 Promise `.catch()` 中递减,仅当计数归零时才清空 `isRefreshing`/`isLogining`,避免首个重试完成即清空标志导致 straggler 触发冗余 refresh/login
- fix(auth): `_handleRequest` refresh 分支第239行条件追加 `&& trigger !== 'login'`,trigger='login' 时跳过 refresh 块和 login 块落到 else reject,终止 refresh+login 都失败后的异常无限循环
- fix(auth): `_request` token 缺失分支增加 `trigger === 'login'` 守卫,login 后 token 仍缺失时直接 `$exportMsg` + `_showFail` + `Promise.reject` 终止,避免再次调用 `_handleTokenLogin` 形成无限微任务循环
- chore(BaseRequest): 删除未使用的 `messageType` 类型导入
### 2.0.15

@@ -12,0 +23,0 @@ - fix(auth): `_handleRequest` login 分支增加 `if (trigger !== 'login')` 守卫,else reject(finalResponse),与 refresh 分支二级降级(refresh→login→reject)一致,避免登录后重试再返回 login 状态时无限循环。

+3
-3
{
"name": "@complex-suite/request",
"version": "2.0.15",
"version": "2.0.16",
"description": "a complex request",

@@ -15,4 +15,4 @@ "type": "module",

"dependencies": {
"@complex-suite/utils": "3.0.15",
"@complex-suite/plugin": "5.0.15"
"@complex-suite/plugin": "5.0.16",
"@complex-suite/utils": "3.0.16"
},

@@ -19,0 +19,0 @@ "devDependencies": {

@@ -1,8 +0,7 @@

import { _Data, jsonToForm } from "@complex-suite/utils"
import { _Data, jsonToForm, deepCloneData } from "@complex-suite/utils"
import { notice } from "@complex-suite/plugin"
import type { messageType } from "@complex-suite/plugin"
import Rule from "./Rule"
import { requestLocale } from "./locale"
import type { RequestMessages } from "./locale"
import type { formatUrlType, RequestInitOption, methodType, failType, totalFailType, failOption, requestTrigger, RequestConfig, responseType } from "./types"
import type { formatUrlType, RequestInitOption, methodType, failType, totalFailType, failOption, requestTrigger, RequestConfig, RequestConfigWithUrl, responseType } from "./types"

@@ -13,3 +12,3 @@ type statusType = {

export type { formatUrlType, RequestInitOption, methodType, failType, totalFailType, failOption, requestTrigger, RequestConfig }
export type { formatUrlType, RequestInitOption, methodType, failType, totalFailType, failOption, requestTrigger, RequestConfig, RequestConfigWithUrl }

@@ -89,2 +88,4 @@ const defaultFormatUrlWithBaseUrl = function(this: BaseRequest, url: string) {

isRefreshing?: Promise<any>
refreshCount: number = 0
loginCount: number = 0
status: statusType

@@ -126,2 +127,6 @@ formatUrl: formatUrlType

}
// 深拷贝 data,隔离调用方与内部对 data 的修改(类型转换、append 等)
if (requestConfig.data) {
requestConfig.data = deepCloneData(requestConfig.data)
}
const targetType = requestConfig.targetType || 'json'

@@ -169,3 +174,3 @@ if (requestConfig.currentType == undefined) {

}
request(requestConfig: Partial<RequestConfig<R, L>>) {
request(requestConfig: RequestConfigWithUrl<R, L>) {
// 浅拷贝入参,避免 _parseRequestConfig 修改 method/url/currentType 等污染调用方原对象

@@ -193,2 +198,9 @@ return this._request(this._parseRequestConfig({ ...requestConfig }))

if (res.token) {
if (trigger === 'login') {
// login 后 token 仍缺失,说明 login 实现有问题,直接 reject 避免无限递归
const msg = requestLocale.t('error.tokenAbsent', { prop: res.prop })
this.$exportMsg(msg)
this._showFail(requestConfig.fail, 'token', msg)
return Promise.reject({ status: 'fail', code: 'token absent after login' })
}
// 存在Token规则但是不存在值,需要调用login接口

@@ -208,5 +220,22 @@ // 此处不应判断是否为重复操作

return new Promise((resolve, reject) => {
this.rule.login('token').then(() => {
this._request(requestConfig, 'login').then(resolve).catch(reject)
}).catch(reject)
// 复用 isLogining 合并并发登录,避免多个请求各自触发 rule.login('token')
if (!this.isLogining) {
this.isLogining = this.rule.login('token')
this.loginCount = 0
}
this.loginCount++
this.isLogining.then(() => {
this._request(requestConfig, 'login').then(resolve).catch(reject).finally(() => {
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
})
}).catch((err) => {
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
reject(err)
})
})

@@ -222,3 +251,3 @@ }

} else if (finalResponse.status === 'refresh') {
if (this.rule.refresh && trigger !== 'refresh') {
if (this.rule.refresh && trigger !== 'refresh' && trigger !== 'login') {
// 接口返回 refresh:token 过期但可刷新。首次调用 rule.refresh() 刷新 token

@@ -228,10 +257,18 @@ // 刷新成功后以 trigger='refresh' 重试请求;刷新失败则直接 reject

this.isRefreshing = this.rule.refresh()
this.refreshCount = 0
}
this.refreshCount++
this.isRefreshing.then(() => {
this._request(requestConfig, 'refresh').then(resolve).catch(reject).finally(() => {
this.isRefreshing = undefined
this.refreshCount--
if (this.refreshCount === 0) {
this.isRefreshing = undefined
}
})
}).catch((err) => {
// refresh 本身失败时也需清空 isRefreshing,避免后续请求复用 rejected Promise 永久卡死
this.isRefreshing = undefined
this.refreshCount--
if (this.refreshCount === 0) {
this.isRefreshing = undefined
}
reject(err)

@@ -244,10 +281,18 @@ })

this.isLogining = this.rule.login('refresh')
this.loginCount = 0
}
this.loginCount++
this.isLogining.then(() => {
this._request(requestConfig, 'login').then(resolve).catch(reject).finally(() => {
this.isLogining = undefined
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
})
}).catch((err) => {
// login 失败时清空 isLogining,避免后续请求复用 rejected Promise 永久卡死
this.isLogining = undefined
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
reject(err)

@@ -264,10 +309,18 @@ })

this.isLogining = this.rule.login('login')
this.loginCount = 0
}
this.loginCount++
this.isLogining.then(() => {
this._request(requestConfig, 'login').then(resolve).catch(reject).finally(() => {
this.isLogining = undefined
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
})
}).catch((err) => {
// login 失败时清空 isLogining,避免后续请求复用 rejected Promise 永久卡死
this.isLogining = undefined
this.loginCount--
if (this.loginCount === 0) {
this.isLogining = undefined
}
reject(err)

@@ -299,15 +352,15 @@ })

abstract $parseError(responseError: unknown): { msg?: string, type: failType, data: unknown }
get(requestConfig: Partial<RequestConfig<R, L>>) {
get(requestConfig: RequestConfigWithUrl<R, L>) {
const config = { ...requestConfig, method: 'get' as const }
return this.request(config)
}
post(requestConfig: Partial<RequestConfig<R, L>>) {
post(requestConfig: RequestConfigWithUrl<R, L>) {
const config = { ...requestConfig, method: 'post' as const }
return this.request(config)
}
form(requestConfig: Partial<RequestConfig<R, L>>) {
form(requestConfig: RequestConfigWithUrl<R, L>) {
const config = { ...requestConfig, method: 'post' as const, currentType: 'form' as const, targetType: 'form' as const }
return this.request(config)
}
json(requestConfig: Partial<RequestConfig<R, L>>) {
json(requestConfig: RequestConfigWithUrl<R, L>) {
// 注意: 此方法并非直接以 application/json 发送数据,而是将 json 对象转换为 FormData 发送。

@@ -314,0 +367,0 @@ // currentType='json' 标识当前 data 为 json 对象,targetType='form' 触发 _parseRequestConfig 中的 jsonToForm 转换。

@@ -5,3 +5,3 @@ import { _Data } from "@complex-suite/utils"

import { requestLocale } from "./locale"
import type { tokenType, responseType, RuleInitOption, formatUrlType } from "./types"
import type { tokenType, responseType, RuleInitOption } from "./types"

@@ -15,6 +15,2 @@ export type { tokenType, responseType, RuleInitOption }

function defaultFormatUrl(url: string) {
return url
}
class Rule<R = Record<PropertyKey, unknown>, L = Record<PropertyKey, unknown>> extends _Data{

@@ -29,4 +25,3 @@ static $name = 'Rule'

login: loginType
refresh: refreshType
formatUrl: formatUrlType
refresh?: refreshType
constructor(initOption: RuleInitOption<R, L>) {

@@ -50,3 +45,2 @@ super()

this.refresh = initOption.refresh
this.formatUrl = initOption.formatUrl || defaultFormatUrl
}

@@ -53,0 +47,0 @@ $appendToken(requestConfig: RequestConfig<R, L>) {

@@ -54,4 +54,3 @@ // request 包类型集中管理

login: loginType // 登录操作,触发于token本地验证失败时\接口login\接口refresh成功后重新调用依然需要refresh时
refresh: refreshType // 刷新操作,触发于请求提示refresh时
formatUrl?: formatUrlType // 格式化对应URL
refresh?: refreshType // 刷新操作,触发于请求提示refresh时
}

@@ -102,1 +101,4 @@

}
/** url 必传的请求配置类型:url 为必填,其余字段均为可选 */
export type RequestConfigWithUrl<R = Record<PropertyKey, unknown>, L = Record<PropertyKey, unknown>> = Partial<Omit<RequestConfig<R, L>, 'url'>> & { url: string }