2026-03-30 18:08:27 +08:00
|
|
|
|
const { getBaseUrl } = require('./constants');
|
|
|
|
|
|
|
|
|
|
|
|
function withAuthHeader(needAuth, header) {
|
|
|
|
|
|
const app = getApp();
|
|
|
|
|
|
if (!needAuth) return header;
|
|
|
|
|
|
const token = app && app.globalData ? app.globalData.accessToken : '';
|
2026-04-03 17:57:08 +08:00
|
|
|
|
const refreshToken = app && app.globalData ? app.globalData.refreshToken : '';
|
2026-03-30 18:08:27 +08:00
|
|
|
|
if (typeof token === 'string' && token.length > 0) {
|
|
|
|
|
|
header = header || {};
|
|
|
|
|
|
header.Authorization = `Bearer ${token}`;
|
2026-04-03 17:57:08 +08:00
|
|
|
|
header['x-access-token'] = `Bearer ${token}`;
|
|
|
|
|
|
header['x-refresh-token'] = `Bearer ${refreshToken}`;
|
2026-03-30 18:08:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
return header;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeError(payload) {
|
|
|
|
|
|
if (!payload) return new Error('Empty response');
|
|
|
|
|
|
// RESTfulResult usually has `errors` and/or `message`
|
|
|
|
|
|
if (typeof payload === 'string') return new Error(payload);
|
|
|
|
|
|
if (payload.errors) return new Error(payload.errors.toString());
|
|
|
|
|
|
if (payload.message) return new Error(payload.message);
|
|
|
|
|
|
return new Error(JSON.stringify(payload));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 17:57:08 +08:00
|
|
|
|
function getHeaderValue(headers, headerName) {
|
|
|
|
|
|
if (!headers || typeof headers !== 'object') return '';
|
|
|
|
|
|
const target = String(headerName || '').toLowerCase();
|
|
|
|
|
|
for (const k in headers) {
|
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(headers, k) && String(k).toLowerCase() === target) {
|
|
|
|
|
|
const v = headers[k];
|
|
|
|
|
|
return typeof v === 'string' ? v : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function tryUpdateAccessTokenFromResponseHeaders(res) {
|
|
|
|
|
|
// wx.request 的返回头在 res.header(部分环境也可能是 res.headers)
|
|
|
|
|
|
const headers = (res && res.header) || (res && res.headers) || null;
|
|
|
|
|
|
const newAccessToken = getHeaderValue(headers, 'x-access-token');
|
|
|
|
|
|
if (!newAccessToken) return;
|
|
|
|
|
|
|
|
|
|
|
|
const app = getApp();
|
|
|
|
|
|
if (app && typeof app.setAuth === 'function') {
|
|
|
|
|
|
app.setAuth({ accessToken: newAccessToken });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wx.setStorageSync('accessToken', newAccessToken);
|
|
|
|
|
|
if (app && app.globalData) app.globalData.accessToken = newAccessToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-30 18:08:27 +08:00
|
|
|
|
function request({
|
|
|
|
|
|
path,
|
|
|
|
|
|
method = 'GET',
|
|
|
|
|
|
data = {},
|
|
|
|
|
|
needAuth = false,
|
2026-04-03 17:57:08 +08:00
|
|
|
|
contentType = 'application/json',
|
|
|
|
|
|
// Internal flag: prevent infinite retry when refresh token也失败
|
|
|
|
|
|
__retry = false
|
2026-03-30 18:08:27 +08:00
|
|
|
|
}) {
|
|
|
|
|
|
const baseUrl = getBaseUrl();
|
|
|
|
|
|
const url = `${baseUrl}${path}`;
|
|
|
|
|
|
|
|
|
|
|
|
const header = withAuthHeader(needAuth, {
|
|
|
|
|
|
'content-type': contentType
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
wx.request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: method.toUpperCase(),
|
|
|
|
|
|
data: method.toUpperCase() === 'GET' ? data : data,
|
|
|
|
|
|
header: header,
|
|
|
|
|
|
success(res) {
|
2026-04-03 17:57:08 +08:00
|
|
|
|
// 如果后端在 Response Headers 下发新的 token,就替换本地 token
|
|
|
|
|
|
tryUpdateAccessTokenFromResponseHeaders(res);
|
|
|
|
|
|
|
2026-03-30 18:08:27 +08:00
|
|
|
|
const payload = res && res.data !== undefined ? res.data : null;
|
|
|
|
|
|
|
|
|
|
|
|
// Most backend uses RESTfulResult wrapper
|
|
|
|
|
|
if (payload && typeof payload === 'object' && typeof payload.succeeded === 'boolean') {
|
|
|
|
|
|
if (payload.succeeded) resolve(payload.data);
|
|
|
|
|
|
else reject(normalizeError(payload));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback
|
|
|
|
|
|
resolve(payload);
|
|
|
|
|
|
},
|
|
|
|
|
|
fail(err) {
|
2026-04-03 17:57:08 +08:00
|
|
|
|
if (err && err.statusCode === 401 && needAuth && !__retry) {
|
|
|
|
|
|
// 401: try refresh token, then retry the original request once.
|
|
|
|
|
|
const originalErr = err;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const auth = require('./auth');
|
|
|
|
|
|
if (auth && typeof auth.refreshAccessToken === 'function') {
|
|
|
|
|
|
auth
|
|
|
|
|
|
.refreshAccessToken()
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
return request({ path, method, data, needAuth, contentType, __retry: true });
|
|
|
|
|
|
})
|
|
|
|
|
|
.then(resolve)
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
reject(originalErr);
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// ignore and fall through to rejecting original error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-30 18:08:27 +08:00
|
|
|
|
reject(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
request
|
|
|
|
|
|
};
|
|
|
|
|
|
|