-
Notifications
You must be signed in to change notification settings - Fork 300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: refactor http as a configurable service #834
base: refactor/develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,127 @@ | ||||||||||||
import { createApp } from 'vue' | ||||||||||||
import { useBroadcastChannel } from '@vueuse/core' | ||||||||||||
import { constants } from '@opentiny/tiny-engine-utils' | ||||||||||||
import { getMetaApi, http } from '@opentiny/tiny-engine' | ||||||||||||
import Login from './Login.vue' | ||||||||||||
import mockData from './mock' | ||||||||||||
|
||||||||||||
const { BROADCAST_CHANNEL } = constants | ||||||||||||
|
||||||||||||
const { post: globalNotify } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify }) | ||||||||||||
const showError = (url, message) => { | ||||||||||||
globalNotify({ | ||||||||||||
type: 'error', | ||||||||||||
title: '接口报错', | ||||||||||||
message: `报错接口: ${url} \n报错信息: ${message ?? ''}` | ||||||||||||
}) | ||||||||||||
} | ||||||||||||
|
||||||||||||
const loginDom = document.createElement('div') | ||||||||||||
document.body.appendChild(loginDom) | ||||||||||||
const loginVM = createApp(Login).mount(loginDom) | ||||||||||||
|
||||||||||||
const procession = { | ||||||||||||
promiseLogin: null, | ||||||||||||
mePromise: {} | ||||||||||||
} | ||||||||||||
|
||||||||||||
window.lowcode = { | ||||||||||||
platformCenter: { | ||||||||||||
Session: { | ||||||||||||
rebuiltCallback: function () { | ||||||||||||
loginVM.closeLogin() | ||||||||||||
|
||||||||||||
procession.mePromise.resolve('login ok') | ||||||||||||
procession.promiseLogin = null | ||||||||||||
procession.mePromise = {} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
||||||||||||
|
||||||||||||
const LOGIN_EXPIRED_CODE = 401 | ||||||||||||
|
||||||||||||
export const errorResponse = (error) => { | ||||||||||||
// 用户信息失效时,弹窗提示登录 | ||||||||||||
const { response } = error | ||||||||||||
|
||||||||||||
Comment on lines
+48
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle Undefined In the Apply this diff to add a check for export const errorResponse = (error) => {
// 用户信息失效时,弹窗提示登录
- const { response } = error
+ const { response } = error || {} 📝 Committable suggestion
Suggested change
|
||||||||||||
const openLogin = () => { | ||||||||||||
return new Promise((resolve, reject) => { | ||||||||||||
if (!procession.promiseLogin) { | ||||||||||||
procession.promiseLogin = loginVM.openLogin(procession, '/api/rebuildSession') | ||||||||||||
procession.promiseLogin.then(() => { | ||||||||||||
getMetaApi('engine.service.http').request(response.config).then(resolve, reject) | ||||||||||||
// http.request(response.config).then(resolve, reject) | ||||||||||||
}) | ||||||||||||
} | ||||||||||||
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid Potential Race Conditions in The Consider using a locking mechanism or initializing |
||||||||||||
}) | ||||||||||||
} | ||||||||||||
|
||||||||||||
if (response?.status === LOGIN_EXPIRED_CODE) { | ||||||||||||
// vscode 插件环境弹出输入框提示登录 | ||||||||||||
if (window.vscodeBridge) { | ||||||||||||
return Promise.resolve(true) | ||||||||||||
} | ||||||||||||
|
||||||||||||
// 浏览器环境弹出小窗登录 | ||||||||||||
if (response?.headers['x-login-url']) { | ||||||||||||
return openLogin() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
// 默认的 error response 显示接口错误 | ||||||||||||
showError(error.config?.url, error?.message) | ||||||||||||
|
||||||||||||
return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message) | ||||||||||||
} | ||||||||||||
|
||||||||||||
function getConfig(env = import.meta.env) { | ||||||||||||
const baseURL = env.VITE_ORIGIN || '' | ||||||||||||
|
||||||||||||
// 仅在本地开发时,启用 withCredentials | ||||||||||||
const dev = env.MODE?.includes('dev') | ||||||||||||
|
||||||||||||
// 获取租户 id | ||||||||||||
const getTenant = () => new URLSearchParams(location.search).get('tenant') | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure The Consider adding a check to ensure const getTenant = () => {
+ if (typeof location === 'undefined') return null
return new URLSearchParams(location.search).get('tenant')
} 📝 Committable suggestion
Suggested change
|
||||||||||||
|
||||||||||||
return { | ||||||||||||
baseURL, | ||||||||||||
withCredentials: false, | ||||||||||||
headers: { | ||||||||||||
'x-lowcode-mode': dev ? 'develop' : null, | ||||||||||||
'x-lowcode-org': getTenant() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
const isVsCodeEnv = window.vscodeBridge | ||||||||||||
let environment = import.meta.env | ||||||||||||
const isDevelopEnv = environment.MODE?.includes('dev') | ||||||||||||
const isMock = environment.VITE_API_MOCK === 'mock' | ||||||||||||
|
||||||||||||
const preRequest = (config) => { | ||||||||||||
if (isDevelopEnv && config.url.match(/\/generate\//)) { | ||||||||||||
config.baseURL = '' | ||||||||||||
} | ||||||||||||
|
||||||||||||
if (isVsCodeEnv) { | ||||||||||||
config.baseURL = '' | ||||||||||||
} | ||||||||||||
|
||||||||||||
return config | ||||||||||||
} | ||||||||||||
|
||||||||||||
const { interceptors } = http | ||||||||||||
|
||||||||||||
export const httpConfig = { | ||||||||||||
config: getConfig, | ||||||||||||
interceptors: { | ||||||||||||
request: [preRequest], | ||||||||||||
response: [interceptors.response[0], errorResponse] | ||||||||||||
}, | ||||||||||||
mock: isMock, | ||||||||||||
mockData: isMock ? mockData : [] | ||||||||||||
} | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,8 @@ export { GenerateCodeService, PluginPanel } from '@opentiny/tiny-engine-common' | |
|
||
export { default as defaultRegistry } from './registry' | ||
|
||
export { default as http } from '@opentiny/tiny-engine-http' | ||
|
||
Comment on lines
+40
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Missing The
🔗 Analysis chainNew HTTP module export aligns with PR objectives. The addition of the To ensure this change doesn't introduce any conflicts or issues, let's verify the module's existence and its usage across the codebase: This script will help us confirm that the module is properly included in the project dependencies and identify any existing usage or potential conflicts. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Verify the existence of the @opentiny/tiny-engine-http module
echo "Checking for @opentiny/tiny-engine-http in package.json:"
jq '.dependencies["@opentiny/tiny-engine-http"] // .devDependencies["@opentiny/tiny-engine-http"]' package.json
# Check for any existing imports of the http module
echo "Checking for existing imports of the http module:"
rg --type js --type ts 'import.*from.*@opentiny/tiny-engine-http'
# Look for any potential conflicts or duplicate exports
echo "Checking for potential conflicts or duplicate exports:"
rg --type js --type ts 'export.*http.*from'
Length of output: 3414 |
||
export * from '@opentiny/tiny-engine-meta-register' | ||
|
||
export { EditorInfoService, AppService } from '@opentiny/tiny-engine-common' |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,32 +9,14 @@ | |||||||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. | ||||||||||
* | ||||||||||
*/ | ||||||||||
|
||||||||||
/* eslint-disable no-undef */ | ||||||||||
import axios, { globalDefaults } from './axios' | ||||||||||
import { createApp } from 'vue' | ||||||||||
import { useBroadcastChannel } from '@vueuse/core' | ||||||||||
import Login from './Login.vue' | ||||||||||
import { getConfig } from './config' | ||||||||||
import mockData from './mock' | ||||||||||
import { constants } from '@opentiny/tiny-engine-utils' | ||||||||||
import { initHook, HOOK_NAME } from '@opentiny/tiny-engine-meta-register' | ||||||||||
import axios from './axios' | ||||||||||
|
||||||||||
const { BROADCAST_CHANNEL } = constants | ||||||||||
|
||||||||||
const { post: globalNotify } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify }) | ||||||||||
|
||||||||||
const procession = { | ||||||||||
promiseLogin: null, | ||||||||||
mePromise: {} | ||||||||||
} | ||||||||||
|
||||||||||
const LOGIN_EXPIRED_CODE = 401 | ||||||||||
|
||||||||||
const loginDom = document.createElement('div') | ||||||||||
document.body.appendChild(loginDom) | ||||||||||
const loginVM = createApp(Login).mount(loginDom) | ||||||||||
|
||||||||||
const showError = (url, message) => { | ||||||||||
globalNotify({ | ||||||||||
type: 'error', | ||||||||||
|
@@ -43,116 +25,75 @@ const showError = (url, message) => { | |||||||||
}) | ||||||||||
} | ||||||||||
|
||||||||||
window.lowcode = { | ||||||||||
platformCenter: { | ||||||||||
Session: { | ||||||||||
rebuiltCallback: function () { | ||||||||||
loginVM.closeLogin() | ||||||||||
const preResponse = (res) => { | ||||||||||
if (res.data?.error) { | ||||||||||
showError(res.config?.url, res?.data?.error?.message) | ||||||||||
|
||||||||||
procession.mePromise.resolve('login ok') | ||||||||||
procession.promiseLogin = null | ||||||||||
procession.mePromise = {} | ||||||||||
} | ||||||||||
} | ||||||||||
return Promise.reject(res.data.error) | ||||||||||
} | ||||||||||
|
||||||||||
return res.data?.data | ||||||||||
} | ||||||||||
|
||||||||||
let http // 封装axios的http实例 | ||||||||||
let environment = import.meta.env // 当前设计器运行环境变量 | ||||||||||
const errorResponse = (error) => { | ||||||||||
// 用户信息失效时,弹窗提示登录 | ||||||||||
const { response } = error | ||||||||||
|
||||||||||
const isVsCodeEnv = window.vscodeBridge | ||||||||||
const isMock = () => environment.VITE_API_MOCK === 'mock' | ||||||||||
// 默认的 error response 显示接口错误 | ||||||||||
showError(error.config?.url, error?.message) | ||||||||||
|
||||||||||
export const createHttp = (options) => { | ||||||||||
// 缓存http实例,避免每个请求重新创建实例 | ||||||||||
if (http && !options.force) { | ||||||||||
return http | ||||||||||
} | ||||||||||
const isDevelopEnv = environment.MODE?.includes('dev') | ||||||||||
const axiosConfig = getConfig(environment) | ||||||||||
http = axios(axiosConfig) | ||||||||||
|
||||||||||
// 如果未指定是否启用 mock,则本地开发时默认启用,模拟数据在 public/mock 目录下 | ||||||||||
const { enableMock = isDevelopEnv } = options | ||||||||||
enableMock && http.mock(mockData) | ||||||||||
return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message) | ||||||||||
} | ||||||||||
|
||||||||||
const preRequest = (config) => { | ||||||||||
if (isDevelopEnv && config.url.match(/\/generate\//)) { | ||||||||||
config.baseURL = '' | ||||||||||
} | ||||||||||
const initialState = { | ||||||||||
http: null | ||||||||||
} | ||||||||||
|
||||||||||
if (isVsCodeEnv) { | ||||||||||
config.baseURL = '' | ||||||||||
export default { | ||||||||||
id: 'engine.service.http', | ||||||||||
options: { | ||||||||||
config: {}, | ||||||||||
interceptors: { | ||||||||||
request: [], | ||||||||||
response: [preResponse, errorResponse] | ||||||||||
}, | ||||||||||
mock: false, | ||||||||||
mockData: [] | ||||||||||
}, | ||||||||||
type: 'MetaService', | ||||||||||
initialState, | ||||||||||
apis: ({ state }) => ({ | ||||||||||
http: state.http, | ||||||||||
request: state.http.request, | ||||||||||
get: state.http.get, | ||||||||||
post: state.http.post, | ||||||||||
put: state.http.put, | ||||||||||
delete: state.http.delete | ||||||||||
}), | ||||||||||
init: ({ state, options }) => { | ||||||||||
const { config, mock = false, mockData, interceptors } = options | ||||||||||
|
||||||||||
let axiosConfig = config | ||||||||||
|
||||||||||
// config 支持函数 | ||||||||||
if (typeof axiosConfig === 'function') { | ||||||||||
axiosConfig = axiosConfig() | ||||||||||
} | ||||||||||
|
||||||||||
return config | ||||||||||
} | ||||||||||
|
||||||||||
// 请求拦截器 | ||||||||||
http.interceptors.request.use(preRequest) | ||||||||||
|
||||||||||
const preResponse = (res) => { | ||||||||||
if (res.data?.error) { | ||||||||||
showError(res.config?.url, res?.data?.error?.message) | ||||||||||
const http = axios(axiosConfig) | ||||||||||
|
||||||||||
return Promise.reject(res.data.error) | ||||||||||
if (mock) { | ||||||||||
http.mock(mockData) | ||||||||||
} | ||||||||||
|
||||||||||
return res.data?.data | ||||||||||
} | ||||||||||
|
||||||||||
const openLogin = () => { | ||||||||||
return new Promise((resolve, reject) => { | ||||||||||
if (!procession.promiseLogin) { | ||||||||||
procession.promiseLogin = loginVM.openLogin(procession, '/api/rebuildSession') | ||||||||||
procession.promiseLogin.then(() => { | ||||||||||
http.request(response.config).then(resolve, reject) | ||||||||||
}) | ||||||||||
} | ||||||||||
}) | ||||||||||
} | ||||||||||
|
||||||||||
const errorResponse = (error) => { | ||||||||||
// 用户信息失效时,弹窗提示登录 | ||||||||||
const { response } = error | ||||||||||
if (response?.status === LOGIN_EXPIRED_CODE) { | ||||||||||
// vscode 插件环境弹出输入框提示登录 | ||||||||||
if (window.vscodeBridge) { | ||||||||||
return Promise.resolve(true) | ||||||||||
} | ||||||||||
|
||||||||||
// 浏览器环境弹出小窗登录 | ||||||||||
if (response?.headers['x-login-url']) { | ||||||||||
return openLogin() | ||||||||||
} | ||||||||||
if (Array.isArray(interceptors.request)) { | ||||||||||
http.interceptors.request.use(...interceptors.request) | ||||||||||
} | ||||||||||
|
||||||||||
showError(error.config?.url, error?.message) | ||||||||||
|
||||||||||
return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message) | ||||||||||
} | ||||||||||
|
||||||||||
// 响应拦截器 | ||||||||||
http.interceptors.response.use(preResponse, errorResponse) | ||||||||||
|
||||||||||
return http | ||||||||||
} | ||||||||||
if (Array.isArray(interceptors.response)) { | ||||||||||
http.interceptors.request.use(...interceptors.response) | ||||||||||
} | ||||||||||
Comment on lines
+94
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incorrectly attaching response interceptors to request interceptors At line 94, response interceptors are being added to the request interceptors chain using Apply this diff to fix the issue: - http.interceptors.request.use(...interceptors.response)
+ http.interceptors.response.use(...interceptors.response) 📝 Committable suggestion
Suggested change
|
||||||||||
|
||||||||||
/** | ||||||||||
* 根据环境不同初始化设置http参数 | ||||||||||
* @param {*} env: 当前环境变量 | ||||||||||
*/ | ||||||||||
export const initHttp = ({ env }) => { | ||||||||||
if (Object.keys(env).length) { | ||||||||||
environment = env | ||||||||||
state.http = http | ||||||||||
} | ||||||||||
const baseURL = environment.VITE_ORIGIN | ||||||||||
// 调用初始化方法前可能已经存在已经实例化的http,需要设置baseURL | ||||||||||
http?.defaults('baseURL', baseURL) | ||||||||||
globalDefaults('baseURL', baseURL) | ||||||||||
http = createHttp({ force: true, enableMock: isMock() }) | ||||||||||
} | ||||||||||
|
||||||||||
export const useHttp = () => createHttp({ enableMock: isMock() }) | ||||||||||
|
||||||||||
initHook(HOOK_NAME.useHttp, useHttp, { useDefaultExport: true }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure Error Messages Are Properly Sanitized
When displaying error messages that include dynamic data such as
url
andmessage
, ensure that they are properly sanitized to prevent potential Cross-Site Scripting (XSS) attacks.Consider sanitizing the inputs or using a library that handles this automatically.