wechat-article / server /utils /proxy-request.ts
asemxin2000's picture
Deploy wechat-article-exporter to Space
6e41657 verified
Raw
History Blame Contribute Delete
5.45 kB
import dayjs from 'dayjs';
import { H3Event, parseCookies } from 'h3';
import { v4 as uuidv4 } from 'uuid';
import { isDev, USER_AGENT } from '~/config';
import { RequestOptions } from '~/server/types';
import { cookieStore, getCookieFromStore } from '~/server/utils/CookieStore';
import { logRequest, logResponse } from '~/server/utils/logger';
/**
* 代理微信公众号请求
* @description 备注:只有登录请求(`action=login`)中的 `set-cookie` 才会被写入到 CookieStore 中
* @param options 请求参数
*/
export async function proxyMpRequest(options: RequestOptions) {
const runtimeConfig = useRuntimeConfig();
const headers = new Headers({
Referer: 'https://mp.weixin.qq.com/',
Origin: 'https://mp.weixin.qq.com',
'User-Agent': USER_AGENT,
'Accept-Encoding': 'identity', // 禁用压缩,避免出现response.clone() bug
});
// 优先读取参数中的 cookie,若无则从 CookieStore 中读取
const cookie: string | null = options.cookie || (await getCookieFromStore(options.event));
if (cookie) {
headers.set('Cookie', cookie);
}
const requestInit: RequestInit = {
method: options.method,
headers: headers,
redirect: options.redirect || 'follow',
};
// 处理参数
if (options.query) {
options.endpoint += '?' + new URLSearchParams(options.query as Record<string, string>).toString();
}
if (options.method === 'POST' && options.body) {
requestInit.body = new URLSearchParams(options.body as Record<string, string>).toString();
}
// 构造请求
const request = new Request(options.endpoint, requestInit);
// 记录请求报文
const requestId = uuidv4().replace(/-/g, '');
if (process.env.NUXT_DEBUG_MP_REQUEST && isDev) {
await logRequest(requestId, request.clone());
}
// 转发请求
const mpResponse = await fetch(request);
// 记录响应报文
if (process.env.NUXT_DEBUG_MP_REQUEST && isDev) {
await logResponse(requestId, mpResponse.clone());
}
let setCookies: string[] = [];
// 处理登录请求的 uuid cookie
if (options.action === 'start_login') {
// 提取出 uuid 这个 cookie,并透传给客户端
setCookies = mpResponse.headers.getSetCookie().filter(cookie => cookie.startsWith('uuid='));
}
// 处理登录成功请求的 cookie
// 只有登录请求才会将 Cookie 数据写入 CookieStore
// 返回给客户端的一个 auth-key 的 cookie
else if (options.action === 'login') {
// 提取出 token 和 cookies
try {
const authKey = crypto.randomUUID().replace(/-/g, '');
const body = await mpResponse.clone().json();
const redirectUrl = body?.redirect_url;
if (!redirectUrl || typeof redirectUrl !== 'string') {
throw new Error(`登录响应中未找到 redirect_url,响应内容: ${JSON.stringify(body)}`);
}
const token = new URL(`http://localhost${redirectUrl}`).searchParams.get('token');
if (!token) {
throw new Error(`redirect_url 中未找到 token 参数: ${redirectUrl}`);
}
console.log('token', token);
const success = await cookieStore.setCookie(authKey, token, mpResponse.headers.getSetCookie());
if (!success) {
throw new Error('cookie 写入 KV 存储失败');
}
console.log('cookie 写入成功');
setCookies = [
`auth-key=${authKey}; Path=/; Expires=${dayjs().add(4, 'days').toString()}; Secure; HttpOnly`,
// 登录成功后,删除浏览器的 uuid cookie
`uuid=EXPIRED; Path=/; Expires=${dayjs().subtract(1, 'days').toString()}; Secure; HttpOnly`,
];
} catch (error) {
console.error('action(login) failed:', error);
// 登录失败时返回错误响应,而不是静默继续
return new Response(JSON.stringify({ base_resp: { ret: -1, err_msg: `登录处理失败: ${error}` } }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// 处理切换公众号的请求
else if (options.action === 'switch_account') {
const authKey = getAuthKeyFromRequest(options.event);
if (authKey) {
setCookies = ['switch_account=1'];
}
}
// 这里是否需要执行?
// 更新 CookieStore 中的 cookie
else {
// updateCookies(options.event, mpResponse.headers.getSetCookie());
}
// 构造返回给客户端的响应
const responseHeaders = new Headers(mpResponse.headers);
responseHeaders.delete('set-cookie');
setCookies.forEach(setCookie => {
responseHeaders.append('set-cookie', setCookie);
});
const finalResponse = new Response(mpResponse.body, {
status: mpResponse.status,
statusText: mpResponse.statusText,
headers: responseHeaders,
});
if (!options.parseJson) {
return finalResponse;
} else {
return finalResponse.json();
}
}
export function getAuthKeyFromRequest(event: H3Event): string {
let authKey = getRequestHeader(event, 'X-Auth-Key');
if (!authKey) {
const cookies = parseCookies(event);
authKey = cookies['auth-key'];
}
return authKey;
}
// function updateCookies(event: H3Event, cookies: string[]): void {
// const authKey = getAuthKeyFromRequest(event);
// if (authKey) {
// cookieStore.updateCookie(authKey, cookies);
// }
// }