170 lines
4.7 KiB
JavaScript
170 lines
4.7 KiB
JavaScript
Vue.component("csrf-token", {
|
||
created: function () {
|
||
this.refreshToken()
|
||
},
|
||
mounted: function () {
|
||
let that = this
|
||
var form = this.$refs.token.form
|
||
|
||
// 监听表单提交,在提交前刷新token并确保更新到 DOM
|
||
form.addEventListener("submit", function (e) {
|
||
// 如果正在刷新,等待刷新完成
|
||
if (that.refreshing) {
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
return false
|
||
}
|
||
|
||
// 阻止默认提交,先刷新 token
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
that.refreshing = true
|
||
|
||
// 刷新 token
|
||
that.refreshToken(function () {
|
||
// 确保 DOM 中的 token 值是最新的
|
||
that.$forceUpdate()
|
||
that.$nextTick(function () {
|
||
var tokenInput = form.querySelector('input[name="csrfToken"]')
|
||
if (tokenInput) {
|
||
tokenInput.value = that.token
|
||
}
|
||
if (that.$refs.token) {
|
||
that.$refs.token.value = that.token
|
||
}
|
||
|
||
// 确保 DOM 已更新后,再触发表单提交
|
||
setTimeout(function () {
|
||
that.refreshing = false
|
||
// 重新触发表单提交
|
||
Tea.runActionOn(form)
|
||
}, 50)
|
||
})
|
||
})
|
||
|
||
return false
|
||
})
|
||
|
||
// 自动刷新
|
||
setInterval(function () {
|
||
that.refreshToken()
|
||
}, 10 * 60 * 1000)
|
||
|
||
// 监听表单提交失败,如果是 CSRF token 错误,自动刷新 token 并重试
|
||
this.setupAutoRetry(form)
|
||
},
|
||
data: function () {
|
||
return {
|
||
token: "",
|
||
retrying: false,
|
||
refreshing: false
|
||
}
|
||
},
|
||
methods: {
|
||
refreshToken: function (callback) {
|
||
let that = this
|
||
Tea.action("/csrf/token")
|
||
.get()
|
||
.success(function (resp) {
|
||
that.token = resp.data.token
|
||
if (callback) {
|
||
callback()
|
||
}
|
||
})
|
||
.fail(function () {
|
||
if (callback) {
|
||
callback()
|
||
}
|
||
})
|
||
},
|
||
setupAutoRetry: function (form) {
|
||
let that = this
|
||
var originalFail = form.getAttribute("data-tea-fail")
|
||
|
||
// 确保 Tea.Vue 存在
|
||
if (typeof Tea === "undefined" || Tea.Vue == null) {
|
||
if (typeof Tea === "undefined") {
|
||
window.Tea = {}
|
||
}
|
||
if (Tea.Vue == null) {
|
||
Tea.Vue = {}
|
||
}
|
||
}
|
||
|
||
// 创建一个包装的 fail 函数
|
||
var wrappedFailName = "csrfAutoRetryFail_" + Math.random().toString(36).substr(2, 9)
|
||
form.setAttribute("data-tea-fail", wrappedFailName)
|
||
|
||
Tea.Vue[wrappedFailName] = function (resp) {
|
||
// 检查是否是 CSRF token 错误
|
||
var isCSRFError = false
|
||
if (resp && resp.message) {
|
||
// 检查消息是否包含 "表单已失效" 或 "001"
|
||
if (resp.message.indexOf("表单已失效") >= 0 || resp.message.indexOf("(001)") >= 0) {
|
||
isCSRFError = true
|
||
}
|
||
}
|
||
// 检查 HTTP 状态码是否为 403 或 400
|
||
if (!isCSRFError && resp && (resp.statusCode === 403 || resp.status === 403 || resp.statusCode === 400 || resp.status === 400)) {
|
||
isCSRFError = true
|
||
}
|
||
|
||
if (isCSRFError) {
|
||
// 如果不是正在重试,则立即刷新 token 并自动重试
|
||
if (!that.retrying) {
|
||
that.retrying = true
|
||
// 立即刷新 token
|
||
that.refreshToken(function () {
|
||
// 强制更新 Vue,确保响应式数据已更新
|
||
that.$forceUpdate()
|
||
|
||
// 使用 $nextTick 等待 Vue 完成 DOM 更新
|
||
that.$nextTick(function () {
|
||
// 直接查找并更新 DOM 中的 input 元素(通过 name 属性)
|
||
var tokenInput = form.querySelector('input[name="csrfToken"]')
|
||
if (tokenInput) {
|
||
tokenInput.value = that.token
|
||
}
|
||
|
||
// 如果 ref 存在,也更新它
|
||
if (that.$refs.token) {
|
||
that.$refs.token.value = that.token
|
||
}
|
||
|
||
// 使用 setTimeout 确保 DOM 已完全更新
|
||
setTimeout(function () {
|
||
// 再次确认 token 值已更新
|
||
var finalTokenInput = form.querySelector('input[name="csrfToken"]')
|
||
if (finalTokenInput && finalTokenInput.value !== that.token) {
|
||
finalTokenInput.value = that.token
|
||
}
|
||
|
||
that.retrying = false
|
||
// 重新触发表单提交
|
||
Tea.runActionOn(form)
|
||
}, 150)
|
||
})
|
||
})
|
||
return // 不调用原始 fail 函数
|
||
} else {
|
||
// 如果正在重试,说明已经刷新过 token,直接调用原始 fail 函数
|
||
if (originalFail && typeof Tea.Vue[originalFail] === "function") {
|
||
return Tea.Vue[originalFail].call(Tea.Vue, resp)
|
||
} else {
|
||
Tea.failResponse(resp)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 不是 CSRF 错误,调用原始 fail 函数或默认处理
|
||
if (originalFail && typeof Tea.Vue[originalFail] === "function") {
|
||
return Tea.Vue[originalFail].call(Tea.Vue, resp)
|
||
} else {
|
||
Tea.failResponse(resp)
|
||
}
|
||
}
|
||
}
|
||
},
|
||
template: `<input type="hidden" name="csrfToken" :value="token" ref="token"/>`
|
||
})
|