jssdk验签

This commit is contained in:
Wang Kejun 2023-04-17 15:47:53 +08:00
parent e1dd80c3ee
commit 89498e443e
4 changed files with 234 additions and 20 deletions

View File

@ -81,5 +81,15 @@ export declare class WechatPublicInstance {
count: number;
noContent?: 0 | 1;
}): Promise<any>;
getTicket(): Promise<string>;
private randomString;
signatureJsSDK(options: {
url: string;
}): Promise<{
signature: any;
noncestr: string;
timestamp: number;
appId: string;
}>;
}
export {};

View File

@ -5,6 +5,7 @@ var tslib_1 = require("tslib");
require('../../fetch');
var crypto_1 = tslib_1.__importDefault(require("crypto"));
var buffer_1 = require("buffer");
var sha1 = require('sha1');
var WechatPublicInstance = /** @class */ (function () {
function WechatPublicInstance(appId, appSecret, accessToken, externalRefreshFn) {
this.appId = appId;
@ -88,7 +89,15 @@ var WechatPublicInstance = /** @class */ (function () {
var result, _a, access_token, openid, unionid, scope, refresh_token, is_snapshotuser, expires_in;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/oauth2/access_token?appid=".concat(this.appId, "&secret=").concat(this.appSecret, "&code=").concat(code, "&grant_type=authorization_code"), { access_token: 'aaa', openid: code, unionid: code, refresh_token: 'aaa', is_snapshotuser: false, expires_in: 30, scope: 'userinfo' })];
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/oauth2/access_token?appid=".concat(this.appId, "&secret=").concat(this.appSecret, "&code=").concat(code, "&grant_type=authorization_code"), {
access_token: 'aaa',
openid: code,
unionid: code,
refresh_token: 'aaa',
is_snapshotuser: false,
expires_in: 30,
scope: 'userinfo',
})];
case 1:
result = _b.sent();
_a = typeof result === 'string' ? JSON.parse(result) : result, access_token = _a.access_token, openid = _a.openid, unionid = _a.unionid, scope = _a.scope, refresh_token = _a.refresh_token, is_snapshotuser = _a.is_snapshotuser, expires_in = _a.expires_in;
@ -111,7 +120,12 @@ var WechatPublicInstance = /** @class */ (function () {
var result, access_token, refresh_token, expires_in, scope;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=".concat(this.appId, "&grant_type=refresh_token&refresh_token=").concat(refreshToken), { access_token: 'aaa', refresh_token: 'aaa', expires_in: 30, scope: 'userinfo' })];
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=".concat(this.appId, "&grant_type=refresh_token&refresh_token=").concat(refreshToken), {
access_token: 'aaa',
refresh_token: 'aaa',
expires_in: 30,
scope: 'userinfo',
})];
case 1:
result = _a.sent();
access_token = result.access_token, refresh_token = result.refresh_token, expires_in = result.expires_in, scope = result.scope;
@ -130,7 +144,11 @@ var WechatPublicInstance = /** @class */ (function () {
var result, nickname, sex, headimgurl;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/userinfo?access_token=".concat(accessToken, "&openid=").concat(openId, "&lang=zh_CN"), { nickname: '码农哥', sex: 1, headimgurl: 'https://www.ertongzy.com/uploads/allimg/161005/2021233Y7-0.jpg' })];
case 0: return [4 /*yield*/, this.access("https://api.weixin.qq.com/sns/userinfo?access_token=".concat(accessToken, "&openid=").concat(openId, "&lang=zh_CN"), {
nickname: '码农哥',
sex: 1,
headimgurl: 'https://www.ertongzy.com/uploads/allimg/161005/2021233Y7-0.jpg',
})];
case 1:
result = _a.sent();
nickname = result.nickname, sex = result.sex, headimgurl = result.headimgurl;
@ -437,6 +455,83 @@ var WechatPublicInstance = /** @class */ (function () {
});
});
};
WechatPublicInstance.prototype.getTicket = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var myInit, token, result, ticket;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
myInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
};
return [4 /*yield*/, this.getAccessToken()];
case 1:
token = _a.sent();
return [4 /*yield*/, this.access("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".concat(token, "&type=jsapi"), {
ticket: "ticket".concat(Date.now()),
expires_in: 30,
}, myInit)];
case 2:
result = (_a.sent());
ticket = result.ticket;
return [2 /*return*/, ticket];
}
});
});
};
WechatPublicInstance.prototype.randomString = function () {
var len = 16;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
/** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
var maxPos = $chars.length;
var pwd = '';
for (var i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
};
WechatPublicInstance.prototype.signatureJsSDK = function (options) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var url, noncestr, timestamp, jsapi_ticket, contentArray, zhimaString;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
url = options.url;
noncestr = this.randomString();
timestamp = parseInt((Date.now() / 1000).toString(), 10);
return [4 /*yield*/, this.getTicket()];
case 1:
jsapi_ticket = _a.sent();
contentArray = {
noncestr: noncestr,
jsapi_ticket: jsapi_ticket,
timestamp: timestamp,
url: url,
};
zhimaString = '';
Object.keys(contentArray)
.sort()
.forEach(function (ele, idx) {
if (idx > 0) {
zhimaString += '&';
}
zhimaString += ele;
zhimaString += '=';
zhimaString += contentArray[ele];
});
return [2 /*return*/, {
signature: sha1(zhimaString),
noncestr: noncestr,
timestamp: timestamp,
appId: this.appId,
}];
}
});
});
};
return WechatPublicInstance;
}());
exports.WechatPublicInstance = WechatPublicInstance;

View File

@ -18,11 +18,13 @@
"@types/node": "^17.0.31",
"ts-node": "~10.9.1",
"tslib": "^2.4.0",
"typescript": "~4.7.4"
"typescript": "~4.7.4",
"@types/sha1": "^1.1.3"
},
"dependencies": {
"@alicloud/pop-core": "^1.7.12",
"isomorphic-fetch": "^3.0.0",
"sha1": "^1.1.1",
"tencentcloud-sdk-nodejs": "^4.0.525",
"ts-md5": "^1.3.1"
}

View File

@ -1,6 +1,8 @@
require('../../fetch');
import crypto from 'crypto';
import { Buffer } from 'buffer';
const sha1 = require('sha1');
// 目前先支持text和news, 其他type文档https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html
// type ServeMessageType = 'text' | 'news' | 'mpnews' | 'mpnewsarticle' | 'image' | 'voice' | 'video' | 'music' | 'msgmenu';/
type TextServeMessageOption = {
@ -38,19 +40,23 @@ export class WechatPublicInstance {
private refreshAccessTokenHandler?: any;
private externalRefreshFn?: (appId: string) => Promise<string>;
constructor(appId: string, appSecret?: string, accessToken?: string, externalRefreshFn?: (appId: string) => Promise<string>) {
constructor(
appId: string,
appSecret?: string,
accessToken?: string,
externalRefreshFn?: (appId: string) => Promise<string>
) {
this.appId = appId;
this.appSecret = appSecret;
this.externalRefreshFn = externalRefreshFn;
if(!appSecret && !externalRefreshFn) {
if (!appSecret && !externalRefreshFn) {
throw new Error('appSecret和externalRefreshFn必须至少支持一个');
}
if (accessToken) {
this.accessToken = accessToken;
}
else {
} else {
this.refreshAccessToken();
}
}
@ -65,7 +71,11 @@ export class WechatPublicInstance {
}
}
private async access(url: string, mockData: any, init?: RequestInit): Promise<any> {
private async access(
url: string,
mockData: any,
init?: RequestInit
): Promise<any> {
if (process.env.NODE_ENV === 'development') {
return mockData;
}
@ -107,10 +117,25 @@ export class WechatPublicInstance {
async code2Session(code: string) {
const result = await this.access(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${this.appId}&secret=${this.appSecret}&code=${code}&grant_type=authorization_code`,
{ access_token: 'aaa', openid: code, unionid: code, refresh_token: 'aaa', is_snapshotuser: false, expires_in: 30, scope: 'userinfo' }
{
access_token: 'aaa',
openid: code,
unionid: code,
refresh_token: 'aaa',
is_snapshotuser: false,
expires_in: 30,
scope: 'userinfo',
}
);
const { access_token, openid, unionid, scope, refresh_token, is_snapshotuser, expires_in } =
typeof result === 'string' ? JSON.parse(result) : result; // 这里微信返回的数据有时候竟然是text/plain
const {
access_token,
openid,
unionid,
scope,
refresh_token,
is_snapshotuser,
expires_in,
} = typeof result === 'string' ? JSON.parse(result) : result; // 这里微信返回的数据有时候竟然是text/plain
return {
accessToken: access_token as string,
@ -127,7 +152,12 @@ export class WechatPublicInstance {
async refreshUserAccessToken(refreshToken: string) {
const result = await this.access(
`https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=${this.appId}&grant_type=refresh_token&refresh_token=${refreshToken}`,
{ access_token: 'aaa', refresh_token: 'aaa', expires_in: 30, scope: 'userinfo' }
{
access_token: 'aaa',
refresh_token: 'aaa',
expires_in: 30,
scope: 'userinfo',
}
);
const { access_token, refresh_token, expires_in, scope } = result;
return {
@ -141,21 +171,28 @@ export class WechatPublicInstance {
async getUserInfo(accessToken: string, openId: string) {
const result = await this.access(
`https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openId}&lang=zh_CN`,
{ nickname: '码农哥', sex: 1, headimgurl: 'https://www.ertongzy.com/uploads/allimg/161005/2021233Y7-0.jpg' }
{
nickname: '码农哥',
sex: 1,
headimgurl:
'https://www.ertongzy.com/uploads/allimg/161005/2021233Y7-0.jpg',
}
);
const { nickname, sex, headimgurl } = result;
return {
nickname: nickname as string,
gender: sex === 1 ? 'male' : sex === 2 ?'female' : undefined,
gender: sex === 1 ? 'male' : sex === 2 ? 'female' : undefined,
avatar: headimgurl as string,
};
}
private async refreshAccessToken(url?: string, init?: RequestInit) {
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}`,
{ access_token: 'mockToken', expires_in: 600 }
);
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}`,
{ access_token: 'mockToken', expires_in: 600 }
);
const { access_token, expires_in } = result;
this.accessToken = access_token;
// 生成下次刷新的定时器
@ -428,4 +465,74 @@ export class WechatPublicInstance {
}
throw new Error(JSON.stringify(result));
}
async getTicket() {
const myInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
};
const token = await this.getAccessToken();
const result = (await this.access(
`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`,
{
ticket: `ticket${Date.now()}`,
expires_in: 30,
},
myInit
)) as {
ticket: string;
expires_in: number;
};
const { ticket } = result;
return ticket;
}
private randomString() {
let len = 16;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
/** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
async signatureJsSDK(options: { url: string }) {
const url = options.url;
const noncestr = this.randomString();
const timestamp = parseInt((Date.now() / 1000).toString(), 10);
const jsapi_ticket = await this.getTicket();
const contentArray = {
noncestr,
jsapi_ticket,
timestamp,
url,
};
let zhimaString = '';
Object.keys(contentArray)
.sort()
.forEach((ele, idx) => {
if (idx > 0) {
zhimaString += '&';
}
zhimaString += ele;
zhimaString += '=';
zhimaString += contentArray[ele as keyof typeof contentArray];
});
return {
signature: sha1(zhimaString),
noncestr,
timestamp,
appId: this.appId,
};
}
}