oak-external-sdk/lib/service/wechat/WechatMp.js

168 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WechatMpInstance = void 0;
const tslib_1 = require("tslib");
require('../../fetch');
const crypto_1 = tslib_1.__importDefault(require("crypto"));
const buffer_1 = require("buffer");
class WechatMpInstance {
appId;
appSecret;
accessToken;
refreshAccessTokenHandler;
externalRefreshFn;
constructor(appId, appSecret, accessToken, externalRefreshFn) {
this.appId = appId;
this.appSecret = appSecret;
this.externalRefreshFn = externalRefreshFn;
if (!appSecret && !externalRefreshFn) {
throw new Error('appSecret和externalRefreshFn必须至少支持一个');
}
if (accessToken) {
this.accessToken = accessToken;
}
else {
this.refreshAccessToken();
}
}
async getAccessToken() {
while (true) {
if (this.accessToken) {
return this.accessToken;
}
await new Promise((resolve) => setTimeout(() => resolve(0), 500));
}
}
async access(url, init, fresh) {
const response = await global.fetch(url, init);
const { headers, status } = response;
if (![200, 201].includes(status)) {
throw new Error(`微信服务器返回不正确应答:${status}`);
}
const contentType = headers['Content-Type'] || headers.get('Content-Type');
if (contentType.includes('application/json')) {
const json = await response.json();
if (typeof json.errcode === 'number' && json.errcode !== 0) {
if ([42001, 40001].includes(json.errcode)) {
if (fresh) {
throw new Error('刚刷新的token不可能马上过期请检查是否有并发刷新token的逻辑');
}
console.log(JSON.stringify(json));
return this.refreshAccessToken(url, init);
}
throw new Error(`调用微信接口返回出错code是${json.errcode},信息是${json.errmsg}`);
}
return json;
}
if (contentType.includes('text') ||
contentType.includes('xml') ||
contentType.includes('html')) {
const data = await response.text();
return data;
}
if (contentType.includes('application/octet-stream')) {
return await response.arrayBuffer();
}
return response;
}
async code2Session(code) {
const result = await this.access(`https://api.weixin.qq.com/sns/jscode2session?appid=${this.appId}&secret=${this.appSecret}&js_code=${code}&grant_type=authorization_code`);
const { session_key, openid, unionid } = JSON.parse(result); // 这里微信返回的数据竟然是text/plain
return {
sessionKey: session_key,
openId: openid,
unionId: unionid,
};
}
async refreshAccessToken(url, init) {
const result = this.externalRefreshFn
? await this.externalRefreshFn(this.appId)
: await this.access(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.appId}&secret=${this.appSecret}`);
const { access_token, expires_in } = result;
this.accessToken = access_token;
if (process.env.NODE_ENV === 'development') {
console.log(`小程序获得新的accessToken。appId:[${this.appId}], token: [${access_token}]`);
}
// 生成下次刷新的定时器
this.refreshAccessTokenHandler = setTimeout(() => {
this.refreshAccessToken();
}, (expires_in - 10) * 1000);
if (url) {
return this.access(url, init, true);
}
}
decryptData(sessionKey, encryptedData, iv, signature) {
const skBuf = buffer_1.Buffer.from(sessionKey, 'base64');
// const edBuf = Buffer.from(encryptedData, 'base64');
const ivBuf = buffer_1.Buffer.from(iv, 'base64');
const decipher = crypto_1.default.createDecipheriv('aes-128-cbc', skBuf, ivBuf);
// 设置自动 padding 为 true删除填充补位
decipher.setAutoPadding(true);
let decoded = decipher.update(encryptedData, 'base64', 'utf8');
decoded += decipher.final('utf8');
const data = JSON.parse(decoded);
if (data.watermark.appid !== this.appId) {
throw new Error('Illegal Buffer');
}
return data;
}
async getMpUnlimitWxaCode({ scene, page, envVersion = 'release', width, autoColor, lineColor, isHyaline, }) {
const token = await this.getAccessToken();
const result = await this.access(`https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${token}`, {
method: 'POST',
headers: {
'Content-type': 'application/json',
Accept: 'image/jpg',
},
body: JSON.stringify({
// access_token: this.accessToken,
scene,
page,
env_version: envVersion,
width,
auto_color: autoColor,
line_color: lineColor,
is_hyaline: isHyaline,
}),
});
return (await result.arrayBuffer());
}
async getUserPhoneNumber(code) {
const token = await this.getAccessToken();
const result = (await this.access(`https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${token}`, {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
code,
}),
}));
return result.phone_info;
}
/**
* 发送订阅消息
* @param param0
* @returns
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html
*/
async sendSubscribedMessage({ templateId, page, openId, data, state, lang, }) {
const token = await this.getAccessToken();
/**
* 实测若用户未订阅会抛出errcode: 43101, errmsg: user refuse to accept the msg
*/
return this.access(`https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${token}`, {
body: JSON.stringify({
template_id: templateId,
page,
touser: openId,
data,
miniprogram_state: state || 'formal',
lang: lang || 'zh_CN',
}),
method: 'post',
});
}
}
exports.WechatMpInstance = WechatMpInstance;