From a325e153c879466e1f981e0b63a1728f94135358 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 15:19:33 +0800 Subject: [PATCH 1/7] =?UTF-8?q?iOS=20=E5=AE=9E=E7=8E=B0=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/WechatLib.mm | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/ios/WechatLib.mm b/ios/WechatLib.mm index 8ece4c1..c94f3f6 100644 --- a/ios/WechatLib.mm +++ b/ios/WechatLib.mm @@ -5,6 +5,13 @@ #import #import #import +#import "WechatAuthSDK.h" + + +@interface WechatLib () +@property (nonatomic, strong) WechatAuthSDK *authSDK; +@property (nonatomic, strong) RCTResponseSenderBlock scanCallback; +@end @implementation WechatLib @@ -20,6 +27,8 @@ RCT_EXPORT_MODULE() self = [super init]; if (self) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURL:) name:@"RCTOpenURLNotification" object:nil]; + self.authSDK = [[WechatAuthSDK alloc] init]; + self.authSDK.delegate = self; } return self; } @@ -644,4 +653,56 @@ RCT_EXPORT_METHOD(openCustomerServiceChat return type; } +#pragma mark - WechatAuthAPIDelegate + +RCT_EXPORT_METHOD(addListener:(NSString *)eventName) { + +} + +RCT_EXPORT_METHOD(removeListeners:(double)count) { + +} + +RCT_EXPORT_METHOD(authByScan:(NSString *)appid + nonceStr:(NSString *)nonceStr + timeStamp:(NSString *)timeStamp + scope:(NSString *)scope + signature:(NSString *)signature + schemeData:(nullable NSString *)schemeData + callback:(RCTResponseSenderBlock)callback) { + self.scanCallback = callback; + [self.authSDK StopAuth]; + [self.authSDK Auth:appid nonceStr:nonceStr timeStamp:timeStamp scope:scope signature:signature schemeData:schemeData]; +} + +//得到二维码 +- (void)onAuthGotQrcode:(UIImage *)image { + NSLog(@"onAuthGotQrcode"); + NSData *imageData = UIImagePNGRepresentation(image); + if (!imageData) { + imageData = UIImageJPEGRepresentation(image, 1); + } + NSString *base64String = [imageData base64EncodedStringWithOptions:0]; + [self.bridge.eventDispatcher sendDeviceEventWithName:@"onAuthGotQrcode" body:@{@"qrcode": base64String}]; +} + +//二维码被扫描 +- (void)onQrcodeScanned { + NSLog(@"onQrcodeScanned"); +} + +//成功登录 +- (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode { + NSLog(@"onAuthFinish"); + if (self.scanCallback) { + self.scanCallback(@[@{@"authCode": authCode?:@"", @"errCode": @(errCode)}]); + self.scanCallback = nil; + } +} + +- (NSArray *)supportedEvents +{ + return @[@"onAuthGotQrcode", @"onQrcodeScanned", @"onAuthFinish"]; +} + @end From f35606726576761f942bc6ec75889582f7e8a553 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 15:22:09 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=AE=89=E5=8D=93=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wechatlib/WeChatLibModule.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/android/src/main/java/com/wechatlib/WeChatLibModule.java b/android/src/main/java/com/wechatlib/WeChatLibModule.java index 3663186..8a00f2b 100644 --- a/android/src/main/java/com/wechatlib/WeChatLibModule.java +++ b/android/src/main/java/com/wechatlib/WeChatLibModule.java @@ -6,6 +6,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Environment; +import android.util.Base64; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; @@ -25,11 +26,17 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.modules.core.RCTNativeAppEventEmitter; +import com.tencent.mm.opensdk.diffdev.DiffDevOAuthFactory; +import com.tencent.mm.opensdk.diffdev.IDiffDevOAuth; +import com.tencent.mm.opensdk.diffdev.OAuthErrCode; +import com.tencent.mm.opensdk.diffdev.OAuthListener; import com.tencent.mm.opensdk.modelbase.BaseReq; import com.tencent.mm.opensdk.modelbase.BaseResp; import com.tencent.mm.opensdk.modelbiz.ChooseCardFromWXCardPackage; @@ -149,6 +156,42 @@ public class WeChatLibModule extends ReactContextBaseJavaModule implements IWXAP } } + private void sendEvent(ReactContext reactContext, String eventName, WritableMap params) { + reactContext.getJSModule(RCTNativeAppEventEmitter.class).emit(eventName, params); + } + + @ReactMethod + public void authByScan(String appid, String nonceStr, String timeStamp, String scope, String signature, String schemeData, final Callback callback) { + if (api == null) { + callback.invoke(NOT_REGISTERED); + return; + } + + IDiffDevOAuth oauth = DiffDevOAuthFactory.getDiffDevOAuth(); + oauth.stopAuth(); + oauth.auth(appid, scope, nonceStr, timeStamp, signature, new OAuthListener() { + @Override + public void onAuthGotQrcode(String var1, byte[] var2){ + WritableMap map = Arguments.createMap(); + String base64String = Base64.encodeToString(var2, Base64.DEFAULT); + map.putString("qrcode", base64String); + sendEvent(getReactApplicationContext(), "onAuthGotQrcode", map); + } + + @Override + public void onQrcodeScanned() { + + } + @Override + public void onAuthFinish(OAuthErrCode var1, String var2){ + WritableMap map = Arguments.createMap(); + map.putString("authCode", var2); + map.putInt("errCode", var1.getCode()); + callback.invoke(map); + } + }); + } + @ReactMethod public void registerApp(String appid, String universalLink, Callback callback) { this.appId = appid; From 80739f7ab7841bbe9e1d6e2c5ec3f452cd6c503a Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 15:37:09 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 2cf24b3..a97e08a 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,36 @@ following fields: | lang | String | The user language | | country | String | The user country | +#### authByScan([scope, nonceStr, timestamp, scope, signature, schemeData, callback]) 微信扫码授权登录 + +- `appid` {String} the appid you get from WeChat dashboard +- `nonceStr` +- `timestamp` +- `scope` 写死 snsapi_userinfo 即可 +- `signature` 签名 +- `schemeData` 留空即可 +- `callback` (object) => void + +调用 authByScan 后,需要监听二维码的获取,展示完二维码,用户扫码登录完成后才会回调 callback,字段如下 + +| field | type | description | +| ------- | ------ | ----------------------------------- | +| errCode | Number | Error Code | +| authCode | String | Auth Code | + + +如何监听二维码的获取呢?如下示例,拿到二维码的本地图片链接后,使用 Image 等标签进行展示即可 + +```js +const qrcodeEmitter = new NativeEventEmitter(NativeModules.WeChat); + +const subscription = qrcodeEmitter.addListener('onAuthGotQrcode', (res) => + console.log(res.qrcode) +); +``` + +如有不懂,可以查看[微信官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Login_via_Scan.html) + #### ShareText(ShareTextMetadata) 分享文本 ShareTextMetadata From 86bc8bd0317eb3c0df83087e4affefeec9351cf1 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 16:51:41 +0800 Subject: [PATCH 4/7] =?UTF-8?q?JS=20=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wechatlib/WeChatLibModule.java | 2 +- ios/WechatLib.mm | 2 +- src/index.d.ts | 10 ++ src/index.js | 153 +++++++++++++++++- 4 files changed, 162 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/wechatlib/WeChatLibModule.java b/android/src/main/java/com/wechatlib/WeChatLibModule.java index 8a00f2b..9c1bd99 100644 --- a/android/src/main/java/com/wechatlib/WeChatLibModule.java +++ b/android/src/main/java/com/wechatlib/WeChatLibModule.java @@ -187,7 +187,7 @@ public class WeChatLibModule extends ReactContextBaseJavaModule implements IWXAP WritableMap map = Arguments.createMap(); map.putString("authCode", var2); map.putInt("errCode", var1.getCode()); - callback.invoke(map); + callback.invoke(null, map); } }); } diff --git a/ios/WechatLib.mm b/ios/WechatLib.mm index c94f3f6..ce0f9f4 100644 --- a/ios/WechatLib.mm +++ b/ios/WechatLib.mm @@ -695,7 +695,7 @@ RCT_EXPORT_METHOD(authByScan:(NSString *)appid - (void)onAuthFinish:(int)errCode AuthCode:(nullable NSString *)authCode { NSLog(@"onAuthFinish"); if (self.scanCallback) { - self.scanCallback(@[@{@"authCode": authCode?:@"", @"errCode": @(errCode)}]); + self.scanCallback(@[[NSNull null], @{@"authCode": authCode?:@"", @"errCode": @(errCode)}]); self.scanCallback = nil; } } diff --git a/src/index.d.ts b/src/index.d.ts index 43c09c9..49756ce 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -46,10 +46,20 @@ declare module 'react-native-wechat-lib' { state?: string; returnKey?: string; } + export interface ScanLoginResp { + nickname?: string; + headimgurl?: string; + openid?: string; + unionid?: string; + errCode?: number; + errStr?: string; + } export function sendAuthRequest( scope: string | string[], state?: string ): Promise; + export function authByScan(appId: string, appSecret: string, onQRGet: (qrcode: string)=>void): Promise; + export interface ShareMetadata { type: | 'news' diff --git a/src/index.js b/src/index.js index c1e98fe..a822b50 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,8 @@ 'use strict'; -import { DeviceEventEmitter, NativeModules, Platform } from 'react-native'; import { EventEmitter } from 'events'; +import { sha1 } from 'js-sha1'; +import { DeviceEventEmitter, NativeEventEmitter, NativeModules, Platform } from 'react-native'; let isAppRegistered = false; let { WeChat, WechatLib } = NativeModules; @@ -170,6 +171,152 @@ const nativeSubscribeMessage = wrapApi(WeChat.subscribeMessage); const nativeChooseInvoice = wrapApi(WeChat.chooseInvoice); const nativeShareFile = wrapApi(WeChat.shareFile); +const nativeScan = wrapApi(WeChat.authByScan); + +// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html +const getAccessToken = async (WeiXinId, WeiXinSecret) => { + let url = + 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + + WeiXinId + + '&secret=' + + WeiXinSecret; + const response = await fetch(url); + const res = await response.json(); + return res.access_token; +}; + +const getSDKTicket = async (accessToken) => { + let url = + 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=2&access_token=' + + accessToken; + const response = await fetch(url); + const res = await response.json(); + return res.ticket; +}; + +const createSignature = ( + WeiXinId, + nonceStr, + sdkTicket, + timestamp +) => { + const origin = + 'appid=' + + WeiXinId + + '&noncestr=' + + nonceStr + + '&sdk_ticket=' + + sdkTicket + + '×tamp=' + + timestamp; + const ret = sha1(origin); + // console.log('wx scan signature', origin, ret); + return ret; +}; + +const getUserInfo = ( + WeiXinId, + WeiXinSecret, + code, + callback +) => { + let accessTokenUrl = + 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' + + WeiXinId + + '&secret=' + + WeiXinSecret + + '&code=' + + code + + '&grant_type=authorization_code'; + fetch(accessTokenUrl) + .then((res) => { + return res.json(); + }) + .then((res) => { + // console.log('wechat get access code success: ', res.access_token); + let userInfoUrl = + 'https://api.weixin.qq.com/sns/userinfo?access_token=' + + res.access_token + + '&openid=' + + res.openid; + fetch(userInfoUrl) + .then((res2) => { + return res2.json(); + }) + .then((json) => { + // console.log('wechat get user info success: ', json); + callback({ + nickname: json.nickname, + headimgurl: json.headimgurl, + openid: json.openid, + unionid: json.unionid, + }); + }) + .catch((e) => { + console.warn('wechat get user info fail ', e); + callback({ error: e }); + }); + }) + .catch((e) => { + console.warn('wechat get access code fail ', e); + callback({ error: e }); + }); +}; + +const generateObjectId = () => { + var timestamp = ((new Date().getTime() / 1000) | 0).toString(16); // eslint-disable-line no-bitwise + return ( + timestamp + + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function () { + return ((Math.random() * 16) | 0).toString(16).toLowerCase(); // eslint-disable-line no-bitwise + }) + ); +} + +/** + * @method authByScan + * @param {String} appId - the app id + * @param {String} appSecret - the app secret + * @param {Function} onQRGet - (qrcode: string) => void + * @return {Promise} + */ +export function authByScan(appId, appSecret, onQRGet) { + return new Promise(async (resolve, reject) => { + const accessToken = await getAccessToken(appId, appSecret); + const ticket = await getSDKTicket(accessToken); + const nonceStr = generateObjectId(); + const timestamp = String(Math.round(Date.now() / 1000)); + const signature = createSignature(appId, nonceStr, ticket, timestamp); + + const qrcodeEmitter = new NativeEventEmitter(NativeModules.WeChat); + + const subscription = qrcodeEmitter.addListener('onAuthGotQrcode', (res) => + onQRGet && onQRGet(res.qrcode) + ); + + const ret = await nativeScan(appId, nonceStr, timestamp, 'snsapi_userinfo', signature, ''); + // console.log('扫码结果', ret) + subscription.remove(); + if (!ret?.authCode) { + reject(new WechatError({ + errStr: 'Auth code 获取失败', + errCode: -1 + })) + return; + } + getUserInfo(appId, appSecret, ret?.authCode, (result) => { + // console.log('扫码登录结果', result) + if (!result.error) { + resolve(result) + } else { + reject(new WechatError({ + errStr: '扫码登录失败' + JSON.stringify(e), + errCode: -2 + })) + } + }); + }); +} /** * @method sendAuthRequest @@ -224,8 +371,8 @@ export function chooseInvoice(data = {}) { const cardItemList = JSON.parse(resp.cardItemList); resp.cards = cardItemList ? cardItemList.map((item) => ({ - cardId: item.card_id, - encryptCode: item.encrypt_code, + cardId: item.card_id, + encryptCode: item.encrypt_code, })) : []; } From d1c5e74ea5744f26c3c1e1874004690505cfe699 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 16:56:30 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a97e08a..0bec521 100644 --- a/README.md +++ b/README.md @@ -154,32 +154,32 @@ following fields: | lang | String | The user language | | country | String | The user country | -#### authByScan([scope, nonceStr, timestamp, scope, signature, schemeData, callback]) 微信扫码授权登录 +#### authByScan([scope, nonceStr, onQRGet]) 微信扫码授权登录 -- `appid` {String} the appid you get from WeChat dashboard -- `nonceStr` -- `timestamp` -- `scope` 写死 snsapi_userinfo 即可 -- `signature` 签名 -- `schemeData` 留空即可 -- `callback` (object) => void +- `appId` {String} the appId you get from WeChat dashboard +- `appSecret` {String} the appSecret you get from WeChat dashboard +- `onQRGet` (String) => void 调用 authByScan 后,需要监听二维码的获取,展示完二维码,用户扫码登录完成后才会回调 callback,字段如下 | field | type | description | | ------- | ------ | ----------------------------------- | | errCode | Number | Error Code | -| authCode | String | Auth Code | +| errStr | String | Error message if any error occurred | +| nickname | String | 微信昵称 | +| headimgurl | String | 微信头像链接 | +| openid | String | openid | +| unionid | String | unionid | -如何监听二维码的获取呢?如下示例,拿到二维码的本地图片链接后,使用 Image 等标签进行展示即可 +示例如下 ```js -const qrcodeEmitter = new NativeEventEmitter(NativeModules.WeChat); - -const subscription = qrcodeEmitter.addListener('onAuthGotQrcode', (res) => - console.log(res.qrcode) -); +const ret = await WeChat.authByScan(WeiXinId, WeiXinSecret, (qrcode) => { + console.log(qrcode) + // 拿到 qrcode 用 Image 去渲染 +}); +console.log('登录信息', ret); ``` 如有不懂,可以查看[微信官方文档](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Login_via_Scan.html) From 59b1e05de0ed37b4a5f0fefd52880e4a37758f77 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 16:58:24 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20sha1=20=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=E4=BE=9D=E8=B5=96=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E7=99=BB=E5=BD=95=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a58bdd6..786450c 100644 --- a/package.json +++ b/package.json @@ -71,14 +71,16 @@ "react-native": "0.70.6", "react-native-builder-bob": "^0.20.0", "release-it": "^15.0.0", - "typescript": "^4.5.2" + "typescript": "^4.5.2", + "js-sha1": "^0.7.0" }, "resolutions": { "@types/react": "17.0.21" }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": "*", + "js-sha1": "*" }, "engines": { "node": ">= 16.0.0" @@ -160,4 +162,4 @@ "dependencies": { "events": "^3.3.0" } -} +} \ No newline at end of file From 6a54386a6f5897dfc15b52f4733e3020a58f21f6 Mon Sep 17 00:00:00 2001 From: Norcy Date: Mon, 19 Aug 2024 16:59:00 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=9B=9E=E6=BB=9A=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index a822b50..638ca8f 100644 --- a/src/index.js +++ b/src/index.js @@ -371,8 +371,8 @@ export function chooseInvoice(data = {}) { const cardItemList = JSON.parse(resp.cardItemList); resp.cards = cardItemList ? cardItemList.map((item) => ({ - cardId: item.card_id, - encryptCode: item.encrypt_code, + cardId: item.card_id, + encryptCode: item.encrypt_code, })) : []; }