import { fetch } from "undici" export interface GetAuthAccessTokenOptions { appId: string appSecret: string code: string } export interface GetAuthAccessTokenResult { accessToken: string expiresAt: number refreshToken: string openid: string scope: string isSnapshotUser: boolean unionid?: string | undefined } export const getAuthAccessToken = async ( options: GetAuthAccessTokenOptions, ): Promise => { const { appId, appSecret, code } = options interface AccessTokenSuccessResponse { access_token: string expires_in: number refresh_token: string openid: string scope: string is_snapshotuser?: 1 unionid?: string } interface AccessTokenErrorResponse { errcode: number errmsg: string } const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${code}&grant_type=authorization_code` try { const now = Date.now() const result = await fetch(url, { method: "get", }) const json = (await result.json()) as AccessTokenSuccessResponse | AccessTokenErrorResponse if ("errcode" in json) { throw new Error(`${json.errcode}: ${json.errmsg}`) } return { accessToken: json.access_token, expiresAt: now + json.expires_in * 1_000, refreshToken: json.refresh_token, openid: json.openid, scope: json.scope, isSnapshotUser: json.is_snapshotuser === 1, unionid: json.unionid, } } catch (exception) { console.error("Error getting access token:", exception) throw exception } } export interface GetUserInfoOptions { accessToken: string openId: string } export interface GetUserInfoResult { openid: string nickname: string sex: number province: string city: string country: string headimgurl: string privilege: string[] unionid: string } export const getUserInfo = async (options: GetUserInfoOptions): Promise => { const { accessToken, openId } = options interface UserInfoSuccessResponse { openid: string nickname: string sex: number province: string city: string country: string headimgurl: string privilege: string[] unionid: string } interface UserInfoErrorResponse { errcode: number errmsg: string } const url = `https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openId}&lang=zh_CN` try { const result = await fetch(url, { method: "get", }) const json = (await result.json()) as UserInfoSuccessResponse | UserInfoErrorResponse if ("errcode" in json) { throw new Error(`${json.errcode}: ${json.errmsg}`) } return json } catch (exception) { console.error("Error getting user info:", exception) throw exception } } export interface WeixinOfficialAccountOauth2Options { appId: string appSecret: string } /** * @see {@link https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html | 微信网页开发 / 网页授权} */ export class WeixinOfficialAccountOauth2 { private readonly appId: string private readonly appSecret: string constructor(options: WeixinOfficialAccountOauth2Options) { const { appId, appSecret } = options this.appId = appId this.appSecret = appSecret } async getAccessToken(code: string): Promise { return await getAuthAccessToken({ appId: this.appId, appSecret: this.appSecret, code, }) } async getUserInfo(accessToken: string, openId: string): Promise { return await getUserInfo({ accessToken, openId, }) } }