feat: wecom-client 新增网页授权能力(授权 URL / code 换身份 / 外部联系人显示名)
This commit is contained in:
@@ -25,6 +25,7 @@ type WecomEnvelope = {
|
||||
access_token?: string;
|
||||
expires_in?: number;
|
||||
invaliduser?: string;
|
||||
userid?: string;
|
||||
follow_user?: Array<{ userid: string }>;
|
||||
external_userid?: string[];
|
||||
external_contact?: {
|
||||
@@ -458,3 +459,98 @@ export async function listExternalContacts(
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export function buildWecomOAuthUrl(params: {
|
||||
corpId: string;
|
||||
agentId: string;
|
||||
redirectUri: string;
|
||||
state: string;
|
||||
}): string {
|
||||
const query = new URLSearchParams({
|
||||
appid: params.corpId,
|
||||
redirect_uri: params.redirectUri,
|
||||
response_type: "code",
|
||||
scope: "snsapi_base",
|
||||
state: params.state,
|
||||
agentid: params.agentId,
|
||||
});
|
||||
return `https://open.weixin.qq.com/connect/oauth2/authorize?${query.toString()}#wechat_redirect`;
|
||||
}
|
||||
|
||||
export type WecomOAuthIdentity = {
|
||||
userId?: string;
|
||||
externalUserId?: string;
|
||||
};
|
||||
|
||||
export async function exchangeWecomOAuthCode(
|
||||
config: WecomConfig,
|
||||
code: string,
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<WecomOAuthIdentity> {
|
||||
if (!hasAppCredentials(config)) {
|
||||
throw new WecomClientError(
|
||||
"企业微信自建应用未配置(WECOM_CORP_ID / WECOM_AGENT_ID / WECOM_SECRET)",
|
||||
400,
|
||||
);
|
||||
}
|
||||
const authorizationCode = bindingValue(code);
|
||||
if (!authorizationCode) {
|
||||
throw new WecomClientError("缺少企业微信授权 code", 400);
|
||||
}
|
||||
const token = await fetchAccessToken(config, fetchImpl);
|
||||
const url = `${WECOM_API_ORIGIN}/cgi-bin/auth/getuserinfo?access_token=${encodeURIComponent(token)}`;
|
||||
const response = await fetchImpl(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json; charset=utf-8" },
|
||||
body: JSON.stringify({ code: authorizationCode }),
|
||||
signal: AbortSignal.timeout(12_000),
|
||||
});
|
||||
const payload = await readEnvelope(response, "企业微信授权身份换取失败");
|
||||
if (!response.ok) {
|
||||
throw new WecomClientError(
|
||||
`企业微信授权身份换取失败(HTTP ${response.status})`,
|
||||
502,
|
||||
);
|
||||
}
|
||||
if (Number(payload.errcode ?? 0) === 40029) {
|
||||
throw new WecomClientError("企业微信授权 code 无效或已过期", 400);
|
||||
}
|
||||
ensureOk(payload, "企业微信授权身份换取失败");
|
||||
const userId = bindingValue(payload.userid);
|
||||
const externalUserId =
|
||||
typeof payload.external_userid === "string"
|
||||
? bindingValue(payload.external_userid)
|
||||
: "";
|
||||
if (!userId && !externalUserId) {
|
||||
throw new WecomClientError("企业微信未返回有效身份", 400);
|
||||
}
|
||||
return {
|
||||
...(userId ? { userId } : {}),
|
||||
...(externalUserId ? { externalUserId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchWecomExternalContactName(
|
||||
config: WecomConfig,
|
||||
externalUserId: string,
|
||||
fetchImpl: FetchLike = fetch,
|
||||
): Promise<string | null> {
|
||||
if (!hasAppCredentials(config)) return null;
|
||||
const externalId = bindingValue(externalUserId);
|
||||
if (!externalId) return null;
|
||||
try {
|
||||
const token = await fetchAccessToken(config, fetchImpl);
|
||||
const url = `${WECOM_API_ORIGIN}/cgi-bin/externalcontact/get?access_token=${encodeURIComponent(token)}&external_userid=${encodeURIComponent(externalId)}`;
|
||||
const response = await fetchImpl(url, {
|
||||
method: "GET",
|
||||
signal: AbortSignal.timeout(12_000),
|
||||
});
|
||||
const payload = await readEnvelope(response, "获取外部联系人详情失败");
|
||||
if (!response.ok) return null;
|
||||
if (Number(payload.errcode ?? 0) !== 0) return null;
|
||||
const name = bindingValue(payload.external_contact?.name);
|
||||
return name || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user