├── .msfignore ├── .prettierrc.cjs ├── weixin.png ├── .eslintrc.cjs ├── renovate.json ├── android ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── theweflex │ │ └── react │ │ ├── WechatPackage.java │ │ └── WechatModule.java ├── proguard-rules.pro └── build.gradle ├── tsconfig.json ├── .editorconfig ├── .gitignore ├── .github ├── workflows │ └── nodejs.yml └── ISSUE_TEMPLATE.md ├── ios ├── RCTWechat.h ├── RCTWechat.xcodeproj │ └── project.pbxproj └── RCTWechat.m ├── react-native-wechat.podspec ├── LICENSE ├── package.json ├── docs ├── build-setup-android.md └── build-setup-ios.md ├── CODE_OF_CONDUCT.md ├── README.md ├── CHANGELOG.md └── src ├── wechatInterface.ts └── index.ts /.msfignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | node_modules 4 | android/build 5 | docs 6 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('@shm-open/eslint-config-bundle/prettier'); 2 | -------------------------------------------------------------------------------- /weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shm-open/react-native-wechat/HEAD/weixin.png -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // 基于rect 2 | module.exports = { 3 | extends: ['@shm-open/eslint-config-bundle/react-native'], 4 | }; 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "devDependencies": { 4 | "automerge": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ReactNativeWechat_compileSdkVersion=28 2 | ReactNativeWechat_buildToolsVersion=28.0.3 3 | ReactNativeWechat_targetSdkVersion=28 4 | ReactNativeWechat_minSdkVersion=16 5 | ReactNativeWechat_WechatSdkVersion=6.8.0 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "lib", 7 | "lib": ["es5", "es2015", "es2016", "es2017"], 8 | "allowSyntheticDefaultImports": true 9 | }, 10 | "include": ["src/*.*"] 11 | } 12 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{html,css,less,json,xml,jade}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [{M,m}akefile] 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /android/build 4 | /android/RCTWeChat.iml 5 | 6 | # iOS 7 | ios/build 8 | 9 | # OSX 10 | # 11 | .DS_Store 12 | 13 | # Xcode 14 | # 15 | build/ 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata 25 | *.xccheckout 26 | *.moved-aside 27 | DerivedData 28 | *.hmap 29 | *.ipa 30 | *.xcuserstate 31 | project.xcworkspace 32 | 33 | 34 | # Android/IntelliJ 35 | build/ 36 | .idea 37 | .gradle 38 | local.properties 39 | *.iml 40 | 41 | 42 | # outputs 43 | /lib 44 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | - run: npm run build --if-present 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Bug report' 3 | labels: bug 4 | assignees: octocat 5 | --- 6 | 7 | Please read and follow the issue templates: 8 | 9 | 1. Bug Report or Documentation Issue or Questions and Help? 10 | 11 | 2. Which `react-native-wechat` version are you using? 12 | 13 | 3. What platform does your issue occur on? (Android/iOS/Both) 14 | 15 | 4. Please provide a clear and concise description of what the bug is as precisely as possible,you can: 16 | 17 | 1) post a screenshot to explain in which case you get the issue? 18 | 2) related `logs`? 19 | 3) show us the code you are using? 20 | 4) others. 21 | 22 | ### Your Environment 23 | 24 | | software | version 25 | | ---------------- | ------- 26 | | react-native-wechat | ^ version 27 | | react-native | ^ version 28 | | node | ^ version 29 | | npm or yarn | ^ version 30 | -------------------------------------------------------------------------------- /ios/RCTWechat.h: -------------------------------------------------------------------------------- 1 | // Created by shihuimiao on 2021/01/22. 2 | // Copyright © 2021 shihuimiao. All rights reserved. 3 | 4 | #import 5 | #import 6 | 7 | #import 8 | #import "WXApi.h" 9 | 10 | #define RCTLaunchFromWXEvent @"LaunchFromWX.Req" 11 | #define RCTWXSendMessageEvent @"SendMessageToWX.Resp" 12 | #define RCTWXSendAuthEvent @"SendAuth.Resp" 13 | #define RCTWXLaunchMiniprogramEvent @"LaunchMiniprogram.Resp" 14 | #define RCTWXPayEvent @"PayReq.Resp" 15 | #define RCTWXOpenBusinessWebViewEvent @"WXOpenBusinessWebview.Resp" 16 | 17 | @interface RCTWechat : NSObject 18 | 19 | @property (nonatomic, copy) NSString *appId; 20 | 21 | // handle AppDelegate openURL 22 | + (BOOL)handleOpenURL:(NSURL *)url; 23 | 24 | + (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity; 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # wechat sdk 24 | -keep class com.tencent.mm.opensdk.** { *; } 25 | -------------------------------------------------------------------------------- /react-native-wechat.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint RCTWeChat.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | require 'json' 10 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 11 | 12 | Pod::Spec.new do |s| 13 | s.name = "react-native-wechat" 14 | s.version = package['version'] 15 | s.summary = "Wechat library" 16 | s.description = package['description'] 17 | s.author = package['author'] 18 | s.homepage = package['homepage'] 19 | s.license = "MIT" 20 | s.platform = :ios, "9.0" 21 | s.source = { :git => "#{s.homepage}", :tag => "v#{s.version}" } 22 | s.source_files = "ios/*.{h,m}" 23 | s.dependency "React" 24 | s.dependency "WechatOpenSDK", '1.8.7.1' 25 | s.requires_arc = true 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present shihuimiao 4 | 5 | Copyright (c) 2017-2020 Yazhong Liu 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def getExtOrDefault(name) { 4 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeWechat_' + name] 5 | } 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.2.2' 14 | } 15 | } 16 | 17 | 18 | 19 | android { 20 | compileSdkVersion getExtOrDefault('compileSdkVersion') 21 | buildToolsVersion getExtOrDefault('buildToolsVersion') 22 | 23 | defaultConfig { 24 | minSdkVersion getExtOrDefault('minSdkVersion') 25 | targetSdkVersion getExtOrDefault('targetSdkVersion') 26 | versionCode 1 27 | versionName "1.0" 28 | } 29 | buildTypes { 30 | release { 31 | consumerProguardFiles 'proguard-rules.pro' 32 | } 33 | } 34 | } 35 | 36 | repositories { 37 | mavenCentral() 38 | google() 39 | maven { 40 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 41 | url "$rootDir/../node_modules/react-native/android" 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation 'com.facebook.react:react-native:+' 47 | api "com.tencent.mm.opensdk:wechat-sdk-android-without-mta:${getExtOrDefault('WechatSdkVersion')}" 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shm-open/react-native-wechat", 3 | "version": "1.3.0", 4 | "description": "react-native library for wechat app", 5 | "type": "module", 6 | "main": "lib/index.js", 7 | "types": "lib/index.d.ts", 8 | "files": [ 9 | "android/", 10 | "ios/", 11 | "lib/", 12 | "*.podspec" 13 | ], 14 | "scripts": { 15 | "build": "npm run clean && npm run compile", 16 | "clean": "rm -rf lib", 17 | "compile": "tsc -d", 18 | "start": "concurrently npm:watch-ts npm:sync-to-app", 19 | "watch-ts": "npm run compile -- --watch", 20 | "sync-to-app": "if [ $SHM_APP_SOURCE_PATH ]; then msf ./ $SHM_APP_SOURCE_PATH/node_modules/@shm-open/react-native-wechat; else tail -f /dev/null; fi", 21 | "release": "npm test && npm run build && standard-version && git push --follow-tags origin master && npm publish", 22 | "test": "eslint src/" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/shm-open/react-native-wechat.git" 27 | }, 28 | "keywords": [ 29 | "wechat", 30 | "react", 31 | "react-native", 32 | "react-component" 33 | ], 34 | "dependencies": { 35 | "@shm-open/utilities": "^1.7.0" 36 | }, 37 | "peerDependencies": { 38 | "react-native": "*" 39 | }, 40 | "devDependencies": { 41 | "@shm-open/eslint-config-bundle": "1.9.13", 42 | "@types/react-native": "0.72.8", 43 | "concurrently": "9.2.1", 44 | "msf-cli": "1.2.5", 45 | "standard-version": "9.5.0", 46 | "typescript": "5.9.3" 47 | }, 48 | "homepage": "https://github.com/shm-open/react-native-wechat", 49 | "author": "shihuimiao", 50 | "license": "MIT" 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/com/theweflex/react/WechatPackage.java: -------------------------------------------------------------------------------- 1 | package com.theweflex.react; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.TurboReactPackage; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.module.annotations.ReactModule; 8 | import com.facebook.react.module.model.ReactModuleInfo; 9 | import com.facebook.react.module.model.ReactModuleInfoProvider; 10 | import com.facebook.react.turbomodule.core.interfaces.TurboModule; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class WechatPackage extends TurboReactPackage implements ReactPackage { 16 | @Override 17 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 18 | switch (name) { 19 | case WechatModule.NAME: 20 | return new WechatModule(reactContext); 21 | default: 22 | throw new IllegalArgumentException("cannot find native module: " + name); 23 | } 24 | } 25 | 26 | @Override 27 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 28 | Class[] moduleList = new Class[]{ 29 | WechatModule.class 30 | }; 31 | 32 | final Map reactModuleInfoMap = new HashMap<>(); 33 | for (Class moduleClass : moduleList) { 34 | ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); 35 | 36 | reactModuleInfoMap.put( 37 | reactModule.name(), 38 | new ReactModuleInfo( 39 | reactModule.name(), 40 | moduleClass.getName(), 41 | true, 42 | reactModule.needsEagerInit(), 43 | reactModule.hasConstants(), 44 | reactModule.isCxxModule(), 45 | TurboModule.class.isAssignableFrom(moduleClass))); 46 | } 47 | 48 | return () -> reactModuleInfoMap; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/build-setup-android.md: -------------------------------------------------------------------------------- 1 | # Build Setup for Android 2 | 3 | Copy lines to `proguard-rules.pro`: 4 | 5 | ```pro 6 | -keep class com.tencent.mm.sdk.** { 7 | *; 8 | } 9 | ``` 10 | 11 | **Integrating with login and share** 12 | 13 | If you are going to integrate login or share functions, you need to 14 | create a package named 'wxapi' in your application package and a class 15 | named `WXEntryActivity` in it. 16 | 17 | ```java 18 | package your.package.wxapi; 19 | 20 | import android.app.Activity; 21 | import android.os.Bundle; 22 | import com.theweflex.react.WechatModule; 23 | 24 | public class WXEntryActivity extends Activity { 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | // 冷启动、热启动自己应用需要自己处理跳转到指定页面 29 | WechatModule.handleIntent(getIntent()); 30 | finish(); 31 | } 32 | } 33 | ``` 34 | 35 | Then add the following node to `AndroidManifest.xml`: 36 | 37 | ```xml 38 | 39 | 40 | 45 | 46 | 47 | ``` 48 | 49 | **Integrating the WeChat Payment** 50 | 51 | If you are going to integrate payment functionality by using this library, then 52 | create a package named also `wxapi` in your application package and a class named 53 | `WXPayEntryActivity`, this is used to bypass the response to JS level: 54 | 55 | ```java 56 | package your.package.wxapi; 57 | 58 | import android.app.Activity; 59 | import android.os.Bundle; 60 | import com.theweflex.react.WechatModule; 61 | 62 | public class WXPayEntryActivity extends Activity { 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | // 冷启动、热启动自己应用需要自己处理跳转到指定页面 67 | WechatModule.handleIntent(getIntent()); 68 | finish(); 69 | } 70 | } 71 | ``` 72 | 73 | Then add the following node to `AndroidManifest.xml`: 74 | 75 | ```xml 76 | 77 | 78 | 83 | 84 | 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/build-setup-ios.md: -------------------------------------------------------------------------------- 1 | # Build Setup for iOS 2 | 3 | ## Installation 4 | 5 | ```sh 6 | $ cd your project iOS path 7 | $ pod install 8 | ``` 9 | 10 | #### URL Schemes 11 | Add "URL Scheme" as your wechat appId for "URL type" in Targets > info, See 12 | the following screenshot for the view on your XCode: 13 | 14 | ![Set URL Scheme in XCode](https://i.loli.net/2019/08/31/yUD2F5MrPKjngo3.jpg) 15 | 16 | Cannot go back to APP from WeChat without configuration. 17 | 如果不配置,就无法从微信重新回到APP。 18 | 19 | 20 | #### LSApplicationQueriesSchemes 21 | On iOS 9+, add `wechat`、 `weixin` and `weixinULAPI` into `LSApplicationQueriesSchemes` in 22 | `Targets` > `info` > `Custom iOS Target Properties`. Or edit `Info.plist` 23 | then add: 24 | 25 | ```xml 26 | LSApplicationQueriesSchemes 27 | 28 | weixin 29 | wechat 30 | weixinULAPI 31 | 32 | ``` 33 | If not configured, apple will prevent you from jumping to WeChat due to security permissions. 34 | 如果不配置,因为安全权限问题,苹果会阻止你跳转到微信。 35 |
36 | 37 | #### AppDelegate 38 | copy the following in `AppDelegate.m`: 39 | 40 | Wechat callback function, If not configured, When sharing is called, it appears "connecting" and then bounces back. 41 | 微信回调方法,如果不配置,分享的时候微信会出现"正在连接",然后直接弹回APP。 42 | 43 | ```objc 44 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url 45 | options:(NSDictionary *)options 46 | { 47 | BOOL handled = [RCTWechat handleOpenURL:url]; 48 | if (handled) { 49 | return handled; 50 | } 51 | return [RCTLinkingManager application:application openURL:url options:options]; 52 | } 53 | 54 | - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { 55 | BOOL handled = [RCTWechat handleOpenUniversalLink:userActivity]; 56 | if (handled) { 57 | return handled; 58 | } 59 | return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 60 | } 61 | ``` 62 | 63 | #### Universal Links config 64 | Universal Links 如果不配置,就无法从微信重新回到APP。
65 | 在`选中project -> TARGETS -> Signing&Capabilities` 然后点击`+`添加一个`Associated Domains`, 在里面填写`Universal Links`配置的网址
66 | 67 | Apple Universal Link: [https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12-SW1](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12-SW1) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at yorkiefixer@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-wechat ![Node.js CI](https://github.com/shm-open/react-native-wechat/workflows/Node.js%20CI/badge.svg) 2 | 3 | [React Native] bridging library that integrates WeChat SDKs 4 | 5 | 本库是在 [react-native-wechat](https://github.com/yorkie/react-native-wechat) 基础上进行重写; 优化了类型定义,方法调用,并且使用`CocoaPods`和`gradle`来管理原生模块依赖的`wechat SDK`. 6 | 7 | ## Table of Contents 8 | 9 | - [Getting Started](#getting-started) 10 | - [API Documentation](#api-documentation) 11 | - [Installation](#installation) 12 | - [Community](#community) 13 | - [Authors](#authors) 14 | - [License](#license) 15 | 16 | ## Getting Started 17 | 18 | `$ npm install @shm-open/react-native-wechat` 19 | 20 | - [Build setup on iOS](./docs/build-setup-ios.md) 21 | - [Build setup on Android](./docs/build-setup-android.md) 22 | 23 | ## API Documentation 24 | 25 | [react-native-wechat] uses Promises, therefore you can use `Promise` 26 | or `async/await` to manage your dataflow. 27 | 28 | #### registerApp(appId, universalLink) 29 | 30 | - `appId` {String} the appId you get from WeChat dashboard 31 | - `universalLink` {String} the iOS universalLink setting 32 | - returns {Boolean} explains if your application is registered done 33 | 34 | This method should be called once globally. 35 | 36 | ```js 37 | import * as WeChat from 'react-native-wechat'; 38 | 39 | WeChat.registerApp('your wxid', 'your universal setting'); 40 | ``` 41 | 42 | #### isWXAppInstalled() 43 | 44 | - returns {Boolean} if WeChat is installed. 45 | 46 | Check if the WeChat app is installed on the device. 47 | 48 | #### isWXAppSupportApi() 49 | 50 | - returns {Boolean} Contains the result. 51 | 52 | Check if wechat support open url. 53 | 54 | #### getApiVersion() 55 | 56 | - returns {String} Contains the result. 57 | 58 | Get the WeChat SDK api version. 59 | 60 | #### openWXApp() 61 | 62 | - returns {Boolean} 63 | 64 | Open the WeChat app from your application. 65 | 66 | #### sendAuthRequest(scope, state) 67 | 68 | - `scope` {String} Scopes of auth request. 69 | snsapi_userinfo or snsapi_base 70 | - `state` {String} 用于保持请求和回调的状态,授权请求后原样带回给第三方 71 | - returns {Object} 72 | 73 | Send authentication request, and it returns an object with the 74 | following fields: 75 | 76 | | field | type | description | 77 | | ------- | ------ | ----------------------------------- | 78 | | errCode | Number | Error Code | 79 | | errStr | String | Error message if any error occurred | 80 | | code | String | Authorization code | 81 | | state | String | state_wx_login | 82 | 83 | #### pay(payload) 84 | 85 | - `partnerId` {String} 商家向财付通申请的商家 id 86 | - `prepayId` {String} 预支付订单 ID 87 | - `nonceStr` {String} 随机串,防重发 88 | - `timeStamp` {String} 时间戳,防重发 89 | - `package` {String} 商家根据财付通文档填写的数据和签名 90 | - `sign` {String} 商家根据微信开放平台文档对数据做的签名 91 | - returns {Object} 92 | 93 | Sends request for proceeding payment, then returns an object: 94 | 95 | | name | type | description | 96 | | ------- | ------ | ----------------------------------- | 97 | | errCode | Number | 0 if pay successed | 98 | | errStr | String | Error message if any error occurred | 99 | 100 | ## Installation 101 | 102 | ```sh 103 | $ npm install react-native-wechat --save 104 | ``` 105 | 106 | ## License 107 | 108 | MIT 109 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.3.0](https://github.com/shm-open/react-native-wechat/compare/v1.2.3...v1.3.0) (2022-02-16) 6 | 7 | 8 | ### Features 9 | 10 | * update sdk to 6.8.0 ([2e41993](https://github.com/shm-open/react-native-wechat/commit/2e419937108b1ecd4ae94cdc7ab458afb363b446)) 11 | 12 | ### [1.2.3](https://github.com/shm-open/react-native-wechat/compare/v1.2.2...v1.2.3) (2021-12-24) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * **android:** use TurboReactPackage to lazy load the native module ([3a25412](https://github.com/shm-open/react-native-wechat/commit/3a25412762e33b340c1c6582b3430b74269171c2)) 18 | 19 | ### [1.2.2](https://github.com/shm-open/react-native-wechat/compare/v1.2.1...v1.2.2) (2021-09-02) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **iOS:** unable to handle login delegate ([8dc71d8](https://github.com/shm-open/react-native-wechat/commit/8dc71d8ab823e28fe1ae9257afc34e23aaf9f5c5)) 25 | 26 | ### [1.2.1](https://github.com/shm-open/react-native-wechat/compare/v1.2.0...v1.2.1) (2021-08-18) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * remove the use of deprecated DeviceEventEmitter.removeListener() ([afbc8fd](https://github.com/shm-open/react-native-wechat/commit/afbc8fd508f6eb58a8bc1a66d51298fb9689e601)) 32 | 33 | ## [1.2.0](https://github.com/shm-open/react-native-wechat/compare/v1.1.2...v1.2.0) (2021-07-22) 34 | 35 | 36 | ### Features 37 | 38 | * **android:** make WechatSdkVersion configurable ([349ddda](https://github.com/shm-open/react-native-wechat/commit/349ddda8587d3c57e8e16b8d6d00720172846701)) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * add lint support and fix issues ([6669f62](https://github.com/shm-open/react-native-wechat/commit/6669f627fcfda08173bc8f394ffb73c4afe31be6)) 44 | * **deps:** update @shm-open/utilities to 1.7.0 ([4202b38](https://github.com/shm-open/react-native-wechat/commit/4202b38972ce56c41591b975c16ebf0309772983)) 45 | * **deps:** update dependency @shm-open/utilities to v1.4.1 ([efe9b31](https://github.com/shm-open/react-native-wechat/commit/efe9b313d81703b8e4544fac5e2b2f63a5d5530b)) 46 | * **deps:** update dependency @shm-open/utilities to v1.6.2 ([7e95906](https://github.com/shm-open/react-native-wechat/commit/7e9590673aaefb6147e12ea9cab61ed25103d96b)) 47 | 48 | ### [1.1.2](https://github.com/shm-open/react-native-wechat/compare/v1.1.1...v1.1.2) (2021-03-08) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * **deps:** update dependency @shm-open/utilities to v1.3.0 ([612a68f](https://github.com/shm-open/react-native-wechat/commit/612a68f3559faef413dadb8f008c3a555a6044b7)) 54 | * **iOS:** fix shareMiniprogram method not response ([16bc085](https://github.com/shm-open/react-native-wechat/commit/16bc085fc155d2d5c29caa2cb1c7f8a5b8e193ab)) 55 | 56 | ### [1.1.1](https://github.com/shm-open/react-native-wechat/compare/v1.1.0...v1.1.1) (2021-02-05) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * **deps:** update dependency @shm-open/utilities to v1.2.2 ([e402e42](https://github.com/shm-open/react-native-wechat/commit/e402e42cca114b2e8372ffc418ba999b8e017adb)) 62 | 63 | ## [1.1.0](https://github.com/shm-open/react-native-wechat/compare/v1.0.1...v1.1.0) (2021-01-29) 64 | 65 | 66 | ### Features 67 | 68 | * rename wechat ([7ce439c](https://github.com/shm-open/react-native-wechat/commit/7ce439cda67089faaeeef9530949013781b207cc)) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * **deps:** update dependency @shm-open/utilities to v1.2.1 ([3c00cb5](https://github.com/shm-open/react-native-wechat/commit/3c00cb5d3fefa18ffc8beb7f5051db985b89351e)) 74 | * 更新gradle,重命名java文件名 ([2c0c8f8](https://github.com/shm-open/react-native-wechat/commit/2c0c8f8d46776f5e2b8b80ca5d2ed11ff1f96fbe)) 75 | 76 | ### [1.0.1](https://github.com/shm-open/react-native-wechat/compare/v1.0.0...v1.0.1) (2021-01-29) 77 | 78 | ## 1.0.0 (2021-01-29) 79 | 80 | 81 | ### Features 82 | 83 | * wechat库重构优化 ([523e13f](https://github.com/shm-open/react-native-wechat/commit/523e13f1f452c8448825abd6cb02290eb83b04db)) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * **example:** upgrade the example project to RN 0.60 ([#507](https://github.com/shm-open/react-native-wechat/issues/507)) ([138f4f8](https://github.com/shm-open/react-native-wechat/commit/138f4f8edac0edf9147ca139c4452cef62919260)) 89 | -------------------------------------------------------------------------------- /src/wechatInterface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 微信小程序类型 3 | */ 4 | export enum WechatMiniprogramTypeEnum { 5 | // 正式版 6 | RELEASE = 0, 7 | // 测试版 8 | TEST = 1, 9 | // 体验版 10 | PREVIEW = 2, 11 | } 12 | 13 | /** 14 | * 分享场景 15 | */ 16 | export enum WechatSceneEnum { 17 | // 聊天界面 18 | SESSION = 0, 19 | // 朋友圈 20 | TIMELINE = 1, 21 | // 收藏 22 | FAVORITE = 2, 23 | } 24 | 25 | /** 26 | * 分享消息基础类型 27 | */ 28 | export interface WechatShareBase { 29 | // 标题 30 | title?: string; 31 | // 描述 32 | description?: string; 33 | } 34 | 35 | /** 36 | * 小程序分享类型 37 | */ 38 | export interface WechatShareMiniprogram extends WechatShareBase { 39 | // 兼容低版本的网页链接(长度不超过10KB) 40 | webpageUrl: string; 41 | // 小程序预览图url 42 | thumbImageUrl: string; 43 | miniprogram: WechatOpenMiniprogram; 44 | } 45 | 46 | /** 47 | * 打开小程序 48 | */ 49 | export interface WechatOpenMiniprogram { 50 | // 小程序userName 51 | userName: string; 52 | // 小程序页面路径 53 | path: string; 54 | // 小程序的类型 55 | miniprogramType?: WechatMiniprogramTypeEnum; 56 | } 57 | 58 | /** 59 | * 分享文本 60 | */ 61 | export interface WechatShareText extends WechatShareBase { 62 | text: string; 63 | // 发送场景 64 | scene: WechatSceneEnum; 65 | } 66 | 67 | /** 68 | * 分享图片 69 | */ 70 | export interface WechatShareImage extends WechatShareBase { 71 | // 图片url 72 | imageUrl: string; 73 | // 发送场景 74 | scene: WechatSceneEnum; 75 | } 76 | 77 | /** 78 | * 分享网页 79 | */ 80 | export interface WechatShareWebPage extends WechatShareBase { 81 | // 网页地址 82 | webpageUrl: string; 83 | // 图片地址 84 | thumbImageUrl: string; 85 | // 发送场景 86 | scene: WechatSceneEnum; 87 | } 88 | 89 | /** 90 | * 分享Video [videoUrl和videoLowBandUrl不能同时为空] 91 | */ 92 | export interface WechatShareVideo extends WechatShareBase { 93 | // 视频链接 94 | videoUrl?: string; 95 | // 供低带宽的环境下使用的视频链接 96 | videoLowBandUrl?: string; 97 | // 缩略图 98 | thumbImageUrl?: string; 99 | } 100 | 101 | /** 102 | * 分享Music [videoUrl和videoLowBandUrl不能同时为空] 103 | */ 104 | export interface WechatShareMusic extends WechatShareBase { 105 | // 音频网页的URL地址 106 | musicUrl?: string; 107 | // 供低带宽环境下使用的音频网页URL地址 108 | musicLowBandUrl?: string; 109 | // 音频数据的URL地址 110 | musicDataUrl?: string; 111 | // 供低带宽环境下使用的音频数据URL地址 112 | musicLowBandDataUrl?: string; 113 | // 缩略图 114 | thumbImageUrl?: string; 115 | } 116 | 117 | /** 118 | * 微信支付 119 | */ 120 | export interface WechatPay { 121 | // 商家向财付通申请的商家id 122 | // eslint-disable-next-line @typescript-eslint/naming-convention 123 | partnerId: string; 124 | // 预支付订单 125 | // eslint-disable-next-line @typescript-eslint/naming-convention 126 | prepayId: string; 127 | // 随机串,防重发 128 | nonceStr: string; 129 | // 时间戳,防重发 130 | timeStamp: string; 131 | // 商家根据财付通文档填写的数据和签名 132 | package: string; 133 | // 商家根据微信开放平台文档对数据做的签名 134 | sign: string; 135 | } 136 | 137 | /** 138 | * 微信签约 139 | */ 140 | export interface WechatEntrust { 141 | // eslint-disable-next-line @typescript-eslint/naming-convention 142 | preEntrustWebId: string; 143 | } 144 | 145 | /** 146 | * 错误码 147 | */ 148 | export enum WXErrCodeEnum { 149 | // eslint-disable-next-line @typescript-eslint/naming-convention 150 | WXSuccess = 0, // 成功 151 | // eslint-disable-next-line @typescript-eslint/naming-convention 152 | WXErrCodeCommon = -1, // 普通错误类型 153 | // eslint-disable-next-line @typescript-eslint/naming-convention 154 | WXErrCodeUserCancel = -2, // 用户点击取消并返回 155 | // eslint-disable-next-line @typescript-eslint/naming-convention 156 | WXErrCodeSentFail = -3, // 发送失败 157 | // eslint-disable-next-line @typescript-eslint/naming-convention 158 | WXErrCodeAuthDeny = -4, // 授权失败 159 | // eslint-disable-next-line @typescript-eslint/naming-convention 160 | WXErrCodeUnsupport = -5, // 微信不支持 161 | // eslint-disable-next-line @typescript-eslint/naming-convention 162 | WXErrBan = -6, // Android禁止 163 | } 164 | 165 | /** 166 | * promises will reject with this error when API call finish with an errCode other than zero. 167 | */ 168 | export class WechatError extends Error { 169 | public code: WXErrCodeEnum; 170 | 171 | constructor(code: WXErrCodeEnum, message: string) { 172 | super(message); 173 | this.name = 'WechatError'; 174 | this.code = code; 175 | } 176 | } 177 | 178 | // 基础返回值 179 | export interface BaseWXResp { 180 | errCode: WXErrCodeEnum; 181 | errStr: string; 182 | } 183 | 184 | // SendMessageToWXResp 返回值定义 185 | export interface SendMessageToWXResp extends BaseWXResp { 186 | lang?: string; 187 | country?: string; 188 | } 189 | 190 | // SendAuthResp 返回值定义 191 | export interface SendAuthResp extends BaseWXResp { 192 | lang?: string; 193 | country?: string; 194 | state?: string; 195 | // eslint-disable-next-line @typescript-eslint/naming-convention 196 | appId?: string; 197 | code?: string; 198 | } 199 | 200 | // LaunchMiniprogramResp 返回值定义 201 | export interface LaunchMiniprogramResp extends BaseWXResp { 202 | // 微信关闭窗口后返回给APP的自定义信息 203 | extMsg?: string; 204 | } 205 | 206 | // PayResp 返回值定义 207 | export interface PayResp extends BaseWXResp { 208 | // 财付通返回给商家的信息 209 | returnKey?: string; 210 | } 211 | 212 | // PayResp 返回值定义 213 | export interface OpenBusinessWebViewResp extends BaseWXResp { 214 | // 第三方程序自定义简单数据,微信终端会回传给第三方程序处理 215 | resultInfo?: string; 216 | // 网页业务类型 217 | businessType?: number; 218 | } 219 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unused-modules */ 2 | import { ListenerHolder, ListenerSubscription } from '@shm-open/utilities'; 3 | import { DeviceEventEmitter, EmitterSubscription, NativeModules } from 'react-native'; 4 | import { 5 | WechatPay, 6 | WechatEntrust, 7 | WechatShareImage, 8 | WechatShareMiniprogram, 9 | WechatShareText, 10 | WechatShareWebPage, 11 | WechatShareVideo, 12 | WechatShareMusic, 13 | WXErrCodeEnum, 14 | WechatOpenMiniprogram, 15 | WechatError, 16 | BaseWXResp, 17 | SendMessageToWXResp, 18 | LaunchMiniprogramResp, 19 | OpenBusinessWebViewResp, 20 | PayResp, 21 | SendAuthResp, 22 | } from './wechatInterface'; 23 | 24 | export { 25 | WechatMiniprogramTypeEnum, 26 | WechatSceneEnum, 27 | WechatShareBase, 28 | WechatShareMiniprogram, 29 | WechatOpenMiniprogram, 30 | WechatShareText, 31 | WechatShareImage, 32 | WechatShareWebPage, 33 | WechatShareVideo, 34 | WechatShareMusic, 35 | WechatPay, 36 | WechatEntrust, 37 | WXErrCodeEnum, 38 | WechatError, 39 | BaseWXResp, 40 | SendMessageToWXResp, 41 | SendAuthResp, 42 | LaunchMiniprogramResp, 43 | PayResp, 44 | OpenBusinessWebViewResp, 45 | } from './wechatInterface'; 46 | 47 | type EventType = 48 | | 'SendMessageToWX.Resp' 49 | | 'LaunchMiniprogram.Resp' 50 | | 'PayReq.Resp' 51 | | 'WXOpenBusinessWebview.Resp' 52 | | 'SendAuth.Resp'; 53 | 54 | /** 55 | * 微信是否已安装 56 | */ 57 | export function isWXAppInstalled(): Promise { 58 | return NativeModules.Wechat.isWXAppInstalled(); 59 | } 60 | 61 | /** 62 | * 注册wechat 63 | */ 64 | export function registerApp(appId: string, universalLink: string): Promise { 65 | return NativeModules.Wechat.registerApp(appId, universalLink); 66 | } 67 | 68 | /** 69 | * 判断当前微信的版本是否支持 70 | */ 71 | export function isWXAppSupportApi(): Promise { 72 | return NativeModules.Wechat.isWXAppSupportApi(); 73 | } 74 | 75 | /** 76 | * 微信的安装地址字符串[only iOS] 77 | */ 78 | export function getWXAppInstallUrl(): Promise { 79 | return new Promise((resolve, reject) => { 80 | NativeModules.Wechat.getWXAppInstallUrl((error: string, installURL: string) => { 81 | if (!error) { 82 | resolve(installURL); 83 | } else { 84 | reject(new WechatError(WXErrCodeEnum.WXErrCodeCommon, error)); 85 | } 86 | }); 87 | }); 88 | } 89 | 90 | /** 91 | * 返回当前微信SDK的版本号[only iOS] 92 | */ 93 | export function getApiVersion(): Promise { 94 | return new Promise((resolve, reject) => { 95 | NativeModules.Wechat.getApiVersion((error: string, version: string) => { 96 | if (!error) { 97 | resolve(version); 98 | } else { 99 | reject(new WechatError(WXErrCodeEnum.WXErrCodeCommon, error)); 100 | } 101 | }); 102 | }); 103 | } 104 | 105 | /** 106 | * 打开微信 107 | */ 108 | export function openWXApp(): Promise { 109 | return NativeModules.Wechat.openWXApp(); 110 | } 111 | 112 | function promiseWrap( 113 | eventType: EventType, 114 | callWXApi: () => Promise, 115 | ): Promise { 116 | return new Promise((resolve, reject) => { 117 | // 异步结果,微信部分API成功结果没有回调。 118 | let subscription: EmitterSubscription; 119 | const listener = (resp: T) => { 120 | if (resp.errCode === WXErrCodeEnum.WXSuccess) { 121 | resolve(resp); 122 | } else { 123 | reject(new WechatError(resp.errCode, resp.errStr)); 124 | } 125 | if (subscription) { 126 | subscription.remove(); 127 | subscription = null; 128 | } 129 | }; 130 | subscription = DeviceEventEmitter.addListener(eventType, listener); 131 | // 同步结果,微信同步成功回调不可信,同步失败回调可信。 132 | // TODO: do we need a timeout? 133 | callWXApi().catch((error: WechatError) => { 134 | if (subscription) { 135 | subscription.remove(); 136 | subscription = null; 137 | } 138 | reject(error); 139 | }); 140 | }); 141 | } 142 | 143 | /** 144 | * @method 微信登录授权 145 | * @param scopes - 第三方程序要向微信申请认证,并请求某些权限 146 | * @param state - 第三方程序本身用来标识其请求的唯一性,最后跳转回第三方程序时,由微信终端回传。 147 | * @return Promise 148 | */ 149 | export function sendAuthRequest( 150 | scopes: 'snsapi_userinfo' | 'snsapi_base', 151 | state?: string, 152 | ): Promise { 153 | return promiseWrap('SendAuth.Resp', () => 154 | NativeModules.Wechat.sendAuthRequest(scopes, state), 155 | ); 156 | } 157 | 158 | /** 159 | * @method 分享文本 160 | * @param shareData - 数据 161 | * @return Promise 162 | */ 163 | export function shareText(shareData: WechatShareText): Promise { 164 | return promiseWrap('SendMessageToWX.Resp', () => 165 | NativeModules.Wechat.shareText(shareData), 166 | ); 167 | } 168 | 169 | /** 170 | * @method 分享图片 171 | * @param shareData - 数据 172 | * @return Promise 173 | */ 174 | export function shareImage(shareData: WechatShareImage): Promise { 175 | return promiseWrap('SendMessageToWX.Resp', () => 176 | NativeModules.Wechat.shareImage(shareData), 177 | ); 178 | } 179 | 180 | /** 181 | * @method 分享网页(媒体数据) 182 | * @param shareData - 数据 183 | * @return Promise 184 | */ 185 | export function shareWebpage(shareData: WechatShareWebPage): Promise { 186 | return promiseWrap('SendMessageToWX.Resp', () => 187 | NativeModules.Wechat.shareWebpage(shareData), 188 | ); 189 | } 190 | 191 | /** 192 | * @method 分享小程序 193 | * @param shareData - 数据 194 | * @return Promise 195 | */ 196 | export function shareMiniprogram(shareData: WechatShareMiniprogram): Promise { 197 | return promiseWrap('SendMessageToWX.Resp', () => 198 | NativeModules.Wechat.shareMiniprogram(shareData), 199 | ); 200 | } 201 | 202 | /** 203 | * @method 分享Video 204 | * @param shareData - 数据 205 | * @return Promise 206 | */ 207 | export function shareVideo(shareData: WechatShareVideo): Promise { 208 | return promiseWrap('SendMessageToWX.Resp', () => 209 | NativeModules.Wechat.shareVideo(shareData), 210 | ); 211 | } 212 | 213 | /** 214 | * @method 分享Music 215 | * @param shareData - 数据 216 | * @return Promise 217 | */ 218 | export function shareMusic(shareData: WechatShareMusic): Promise { 219 | return promiseWrap('SendMessageToWX.Resp', () => 220 | NativeModules.Wechat.shareMusic(shareData), 221 | ); 222 | } 223 | 224 | /** 225 | * @method 打开小程序 226 | * @param shareData - 数据 227 | * @return Promise 228 | */ 229 | export function openMiniprogram(shareData: WechatOpenMiniprogram): Promise { 230 | return promiseWrap('LaunchMiniprogram.Resp', () => 231 | NativeModules.Wechat.openMiniprogram(shareData), 232 | ); 233 | } 234 | 235 | /** 236 | * @method 微信支付 237 | * @param payData - 数据 238 | * @return Promise 239 | */ 240 | export function pay(payData: WechatPay): Promise { 241 | return promiseWrap('PayReq.Resp', () => NativeModules.Wechat.pay(payData)); 242 | } 243 | 244 | /** 245 | * @method 微信签约 246 | * @param entrustData - 数据 247 | * @return Promise 248 | */ 249 | export function entrust(entrustData: WechatEntrust): Promise { 250 | return promiseWrap('WXOpenBusinessWebview.Resp', () => 251 | NativeModules.Wechat.entrust(entrustData), 252 | ); 253 | } 254 | 255 | // Wechat handler APP 256 | export interface LaunchFromWXReq { 257 | lang?: string; 258 | country?: string; 259 | extInfo?: string; 260 | } 261 | type LaunchFromWXReqEventType = 'LaunchFromWX.Req'; 262 | type LaunchFromWXReqEventHandler = (data: LaunchFromWXReq) => void; 263 | 264 | type WechatEventType = LaunchFromWXReqEventType; 265 | type WechatEventHandler = LaunchFromWXReqEventHandler; 266 | const handlerHolder = new ListenerHolder(); 267 | 268 | export function addWechatListener( 269 | type: LaunchFromWXReqEventType, 270 | listener: LaunchFromWXReqEventHandler, 271 | ); 272 | 273 | export function addWechatListener( 274 | type: WechatEventType, 275 | listener: WechatEventHandler, 276 | ): ListenerSubscription { 277 | return handlerHolder.addListener(type, listener); 278 | } 279 | 280 | DeviceEventEmitter.addListener('LaunchFromWX.Req', (data) => { 281 | handlerHolder.dispatch('LaunchFromWX.Req', data); 282 | }); 283 | -------------------------------------------------------------------------------- /ios/RCTWechat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 10D6D7A91F160C250066F0F3 /* libRCTWechat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 86D238421BD0BB9E00C75D01 /* libRCTWechat.a */; }; 11 | 867B79F6235EB79E00EE529A /* RCTWechat.m in Sources */ = {isa = PBXBuildFile; fileRef = 867B79F4235EB79E00EE529A /* RCTWechat.m */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXContainerItemProxy section */ 15 | 10D6D7AA1F160C250066F0F3 /* PBXContainerItemProxy */ = { 16 | isa = PBXContainerItemProxy; 17 | containerPortal = 86D2383A1BD0BB9E00C75D01 /* Project object */; 18 | proxyType = 1; 19 | remoteGlobalIDString = 86D238411BD0BB9E00C75D01; 20 | remoteInfo = RCTWechat; 21 | }; 22 | /* End PBXContainerItemProxy section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | 86D238401BD0BB9E00C75D01 /* CopyFiles */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = "include/$(PRODUCT_NAME)"; 29 | dstSubfolderSpec = 16; 30 | files = ( 31 | ); 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 10D6D7A41F160C250066F0F3 /* RCTWechatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RCTWechatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 867B79F4235EB79E00EE529A /* RCTWechat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWechat.m; sourceTree = ""; }; 39 | 867B79F5235EB79E00EE529A /* RCTWechat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWechat.h; sourceTree = ""; }; 40 | 86D238421BD0BB9E00C75D01 /* libRCTWechat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWechat.a; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 10D6D7A11F160C250066F0F3 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | 10D6D7A91F160C250066F0F3 /* libRCTWechat.a in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | 86D2383F1BD0BB9E00C75D01 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 86D238391BD0BB9E00C75D01 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 867B79F5235EB79E00EE529A /* RCTWechat.h */, 66 | 867B79F4235EB79E00EE529A /* RCTWechat.m */, 67 | 86D238431BD0BB9E00C75D01 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | 86D238431BD0BB9E00C75D01 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 86D238421BD0BB9E00C75D01 /* libRCTWechat.a */, 75 | 10D6D7A41F160C250066F0F3 /* RCTWechatTests.xctest */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | /* End PBXGroup section */ 81 | 82 | /* Begin PBXNativeTarget section */ 83 | 10D6D7A31F160C250066F0F3 /* RCTWechatTests */ = { 84 | isa = PBXNativeTarget; 85 | buildConfigurationList = 10D6D7AC1F160C250066F0F3 /* Build configuration list for PBXNativeTarget "RCTWechatTests" */; 86 | buildPhases = ( 87 | 10D6D7A01F160C250066F0F3 /* Sources */, 88 | 10D6D7A11F160C250066F0F3 /* Frameworks */, 89 | 10D6D7A21F160C250066F0F3 /* Resources */, 90 | ); 91 | buildRules = ( 92 | ); 93 | dependencies = ( 94 | 10D6D7AB1F160C250066F0F3 /* PBXTargetDependency */, 95 | ); 96 | name = RCTWechatTests; 97 | productName = RCTWechatTests; 98 | productReference = 10D6D7A41F160C250066F0F3 /* RCTWechatTests.xctest */; 99 | productType = "com.apple.product-type.bundle.unit-test"; 100 | }; 101 | 86D238411BD0BB9E00C75D01 /* RCTWechat */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = 86D2384B1BD0BB9E00C75D01 /* Build configuration list for PBXNativeTarget "RCTWechat" */; 104 | buildPhases = ( 105 | 86D2383E1BD0BB9E00C75D01 /* Sources */, 106 | 86D2383F1BD0BB9E00C75D01 /* Frameworks */, 107 | 86D238401BD0BB9E00C75D01 /* CopyFiles */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = RCTWechat; 114 | productName = RCTWechat; 115 | productReference = 86D238421BD0BB9E00C75D01 /* libRCTWechat.a */; 116 | productType = "com.apple.product-type.library.static"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | 86D2383A1BD0BB9E00C75D01 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | LastUpgradeCheck = 0830; 125 | ORGANIZATIONNAME = WeFlex; 126 | TargetAttributes = { 127 | 10D6D7A31F160C250066F0F3 = { 128 | CreatedOnToolsVersion = 8.3.2; 129 | DevelopmentTeam = QMP96B5DPW; 130 | ProvisioningStyle = Automatic; 131 | }; 132 | 86D238411BD0BB9E00C75D01 = { 133 | CreatedOnToolsVersion = 7.0; 134 | DevelopmentTeam = JP4369ZCVM; 135 | }; 136 | }; 137 | }; 138 | buildConfigurationList = 86D2383D1BD0BB9E00C75D01 /* Build configuration list for PBXProject "RCTWechat" */; 139 | compatibilityVersion = "Xcode 3.2"; 140 | developmentRegion = English; 141 | hasScannedForEncodings = 0; 142 | knownRegions = ( 143 | English, 144 | Base, 145 | ); 146 | mainGroup = 86D238391BD0BB9E00C75D01; 147 | productRefGroup = 86D238431BD0BB9E00C75D01 /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | 86D238411BD0BB9E00C75D01 /* RCTWechat */, 152 | 10D6D7A31F160C250066F0F3 /* RCTWechatTests */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 10D6D7A21F160C250066F0F3 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | 10D6D7A01F160C250066F0F3 /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | 86D2383E1BD0BB9E00C75D01 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 867B79F6235EB79E00EE529A /* RCTWechat.m in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXTargetDependency section */ 186 | 10D6D7AB1F160C250066F0F3 /* PBXTargetDependency */ = { 187 | isa = PBXTargetDependency; 188 | target = 86D238411BD0BB9E00C75D01 /* RCTWechat */; 189 | targetProxy = 10D6D7AA1F160C250066F0F3 /* PBXContainerItemProxy */; 190 | }; 191 | /* End PBXTargetDependency section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | 10D6D7AD1F160C250066F0F3 /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | CLANG_ANALYZER_NONNULL = YES; 198 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 199 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 200 | CLANG_WARN_INFINITE_RECURSION = YES; 201 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 202 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 203 | DEVELOPMENT_TEAM = QMP96B5DPW; 204 | INFOPLIST_FILE = RCTWechatTests/Info.plist; 205 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 206 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 207 | PRODUCT_BUNDLE_IDENTIFIER = yorkie.RCTWechatTests; 208 | PRODUCT_NAME = "$(TARGET_NAME)"; 209 | }; 210 | name = Debug; 211 | }; 212 | 10D6D7AE1F160C250066F0F3 /* Release */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | CLANG_ANALYZER_NONNULL = YES; 216 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 221 | DEVELOPMENT_TEAM = QMP96B5DPW; 222 | INFOPLIST_FILE = RCTWechatTests/Info.plist; 223 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 224 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 225 | PRODUCT_BUNDLE_IDENTIFIER = yorkie.RCTWechatTests; 226 | PRODUCT_NAME = "$(TARGET_NAME)"; 227 | }; 228 | name = Release; 229 | }; 230 | 86D238491BD0BB9E00C75D01 /* Debug */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = dwarf; 251 | ENABLE_STRICT_OBJC_MSGSEND = YES; 252 | ENABLE_TESTABILITY = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_DYNAMIC_NO_PIC = NO; 255 | GCC_NO_COMMON_BLOCKS = YES; 256 | GCC_OPTIMIZATION_LEVEL = 0; 257 | GCC_PREPROCESSOR_DEFINITIONS = ( 258 | "DEBUG=1", 259 | "$(inherited)", 260 | ); 261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 263 | GCC_WARN_UNDECLARED_SELECTOR = YES; 264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 265 | GCC_WARN_UNUSED_FUNCTION = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 268 | MTL_ENABLE_DEBUG_INFO = YES; 269 | ONLY_ACTIVE_ARCH = YES; 270 | SDKROOT = iphoneos; 271 | }; 272 | name = Debug; 273 | }; 274 | 86D2384A1BD0BB9E00C75D01 /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 279 | CLANG_CXX_LIBRARY = "libc++"; 280 | CLANG_ENABLE_MODULES = YES; 281 | CLANG_ENABLE_OBJC_ARC = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INFINITE_RECURSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_NS_ASSERTIONS = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_NO_COMMON_BLOCKS = YES; 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 306 | MTL_ENABLE_DEBUG_INFO = NO; 307 | SDKROOT = iphoneos; 308 | VALIDATE_PRODUCT = YES; 309 | }; 310 | name = Release; 311 | }; 312 | 86D2384C1BD0BB9E00C75D01 /* Debug */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | HEADER_SEARCH_PATHS = ( 316 | "$(inherited)", 317 | "$(SRCROOT)/../../React/**", 318 | "$(SRCROOT)/../react-native/React/**", 319 | "$(SRCROOT)/../node_modules/react-native/React/**", 320 | "$(SRCROOT)/../node_modules/react-native/Libraries/Image/**", 321 | ); 322 | LIBRARY_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "$(PROJECT_DIR)", 325 | ); 326 | OTHER_LDFLAGS = "-ObjC"; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SKIP_INSTALL = YES; 329 | }; 330 | name = Debug; 331 | }; 332 | 86D2384D1BD0BB9E00C75D01 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | HEADER_SEARCH_PATHS = ( 336 | "$(inherited)", 337 | "$(SRCROOT)/../../React/**", 338 | "$(SRCROOT)/../react-native/React/**", 339 | "$(SRCROOT)/../node_modules/react-native/React/**", 340 | "$(SRCROOT)/../node_modules/react-native/Libraries/Image/**", 341 | ); 342 | LIBRARY_SEARCH_PATHS = ( 343 | "$(inherited)", 344 | "$(PROJECT_DIR)", 345 | ); 346 | OTHER_LDFLAGS = "-ObjC"; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | SKIP_INSTALL = YES; 349 | }; 350 | name = Release; 351 | }; 352 | /* End XCBuildConfiguration section */ 353 | 354 | /* Begin XCConfigurationList section */ 355 | 10D6D7AC1F160C250066F0F3 /* Build configuration list for PBXNativeTarget "RCTWechatTests" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 10D6D7AD1F160C250066F0F3 /* Debug */, 359 | 10D6D7AE1F160C250066F0F3 /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | 86D2383D1BD0BB9E00C75D01 /* Build configuration list for PBXProject "RCTWechat" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | 86D238491BD0BB9E00C75D01 /* Debug */, 368 | 86D2384A1BD0BB9E00C75D01 /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | 86D2384B1BD0BB9E00C75D01 /* Build configuration list for PBXNativeTarget "RCTWechat" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | 86D2384C1BD0BB9E00C75D01 /* Debug */, 377 | 86D2384D1BD0BB9E00C75D01 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | /* End XCConfigurationList section */ 383 | }; 384 | rootObject = 86D2383A1BD0BB9E00C75D01 /* Project object */; 385 | } 386 | -------------------------------------------------------------------------------- /ios/RCTWechat.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTWechat.m 3 | // RCTWechat 4 | // 5 | // Created by shihuimiao on 2021/01/22. 6 | // Copyright © 2021 shihuimiao. All rights reserved. 7 | // 8 | 9 | #import "RCTWechat.h" 10 | #import "WXApiObject.h" 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | // Define error messages 17 | #define NOT_REGISTERED (@"registerApp required") 18 | #define INVOKE_FAILED (@"Wechat API invoke returns false") 19 | 20 | // pending LaunchFromWX event if wechat not register 21 | static NSURL *pendingWeChatDelegateURL = nil; 22 | // wechat实例,如果为nil,代表SDK未初始化 23 | static RCTWechat *instanceManager = nil; 24 | 25 | static inline NSString* getWXCommonErrorCode() { 26 | return [NSString stringWithFormat:@"%d", WXErrCodeCommon]; 27 | } 28 | 29 | @implementation RCTWechat 30 | 31 | @synthesize bridge = _bridge; 32 | 33 | RCT_EXPORT_MODULE(Wechat) 34 | 35 | - (dispatch_queue_t)methodQueue 36 | { 37 | return dispatch_get_main_queue(); 38 | } 39 | 40 | + (BOOL)requiresMainQueueSetup 41 | { 42 | return YES; 43 | } 44 | 45 | + (BOOL)handleOpenURL:(NSURL *)url { 46 | if (!instanceManager) { // wechat sdk not init 47 | pendingWeChatDelegateURL = url; 48 | return NO; 49 | } 50 | return [WXApi handleOpenURL:url delegate:instanceManager]; 51 | } 52 | 53 | + (BOOL)handleOpenUniversalLink:(NSUserActivity *)userActivity { 54 | if (!instanceManager) { // wechat sdk not init 55 | pendingWeChatDelegateURL = userActivity.webpageURL; 56 | return NO; 57 | } 58 | 59 | return [WXApi handleOpenUniversalLink:userActivity delegate:instanceManager]; 60 | } 61 | 62 | - (void)handleWechatDeepLink { 63 | if (pendingWeChatDelegateURL) { 64 | [WXApi handleOpenURL:pendingWeChatDelegateURL delegate:instanceManager]; 65 | pendingWeChatDelegateURL = nil; 66 | return; 67 | } 68 | } 69 | 70 | - (void)dealloc 71 | { 72 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 73 | } 74 | 75 | - (void)createImageRequest:(NSString *)url 76 | callback:(RCTImageLoaderCompletionBlock)callback 77 | { 78 | if (!url) { 79 | NSError *error = [NSError errorWithDomain:@"com.wechat.share" 80 | code:-1 81 | userInfo:@{NSLocalizedDescriptionKey:@"The value of url cannot be empty"}]; 82 | callback(error, nil); 83 | return; 84 | } 85 | NSURL *imageURL = [NSURL URLWithString:url]; 86 | if (!imageURL) { 87 | NSError *error = [NSError errorWithDomain:@"com.wechat.share" 88 | code:-1 89 | userInfo:@{NSLocalizedDescriptionKey:@"imageURL is invalid"}]; 90 | callback(error, nil); 91 | return; 92 | } 93 | NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageURL]; 94 | [[self.bridge moduleForClass:[RCTImageLoader class]] loadImageWithURLRequest:imageRequest callback:callback]; 95 | } 96 | 97 | 98 | /// 图片压缩 99 | /// @param image 待压缩的图片 100 | /// @param maxLength 压缩到多大(单位:byte) 101 | - (NSData *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength 102 | { 103 | CGFloat compression = 1; 104 | NSData *data = UIImageJPEGRepresentation(image, compression); 105 | if (data.length < maxLength) 106 | { 107 | return data; 108 | } 109 | 110 | CGFloat max = 1; 111 | CGFloat min = 0; 112 | for (int i = 0; i < 6; ++i) 113 | { 114 | compression = (max + min) / 2; 115 | data = UIImageJPEGRepresentation(image, compression); 116 | if (data.length < maxLength * 0.9) 117 | { 118 | min = compression; 119 | } 120 | else if (data.length > maxLength) 121 | { 122 | max = compression; 123 | } 124 | else 125 | { 126 | break; 127 | } 128 | } 129 | UIImage *resultImage = [UIImage imageWithData:data]; 130 | if (data.length < maxLength) return data; 131 | 132 | // Compress by size 133 | NSUInteger lastDataLength = 0; 134 | while (data.length > maxLength && data.length != lastDataLength) 135 | { 136 | lastDataLength = data.length; 137 | CGFloat ratio = (CGFloat)maxLength / data.length; 138 | CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)), 139 | (NSUInteger)(resultImage.size.height * sqrtf(ratio))); 140 | UIGraphicsBeginImageContext(size); 141 | [resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; 142 | resultImage = UIGraphicsGetImageFromCurrentImageContext(); 143 | UIGraphicsEndImageContext(); 144 | data = UIImageJPEGRepresentation(resultImage, compression); 145 | } 146 | 147 | if (data.length > maxLength) 148 | { 149 | return [self compressImage:resultImage toByte:maxLength]; 150 | } 151 | 152 | return data; 153 | } 154 | 155 | /// wechat sdk register 156 | /// @param callback callback 157 | RCT_EXPORT_METHOD(registerApp:(NSString *)appid 158 | universalLink:(NSString *)universalLink 159 | resolver:(RCTPromiseResolveBlock)resolve 160 | rejecter:(RCTPromiseRejectBlock)reject) 161 | { 162 | instanceManager = self; 163 | self.appId = appid; 164 | 165 | BOOL registerAPP = [WXApi registerApp:appid universalLink:universalLink]; 166 | if (registerAPP) { 167 | resolve([NSNull null]); 168 | } 169 | else { 170 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 171 | } 172 | [self handleWechatDeepLink]; 173 | } 174 | 175 | RCT_EXPORT_METHOD(isWXAppInstalled:(RCTPromiseResolveBlock)resolve 176 | rejecter:(RCTPromiseRejectBlock)reject) 177 | { 178 | resolve(@([WXApi isWXAppInstalled])); 179 | } 180 | 181 | RCT_EXPORT_METHOD(isWXAppSupportApi:(RCTPromiseResolveBlock)resolve 182 | rejecter:(RCTPromiseRejectBlock)reject) 183 | { 184 | resolve(@([WXApi isWXAppSupportApi])); 185 | } 186 | 187 | // only iOS 188 | RCT_EXPORT_METHOD(getWXAppInstallUrl:(RCTResponseSenderBlock)callback) 189 | { 190 | callback(@[[NSNull null], [WXApi getWXAppInstallUrl]]); 191 | } 192 | 193 | // only iOS 194 | RCT_EXPORT_METHOD(getApiVersion:(RCTResponseSenderBlock)callback) 195 | { 196 | callback(@[[NSNull null], [WXApi getApiVersion]]); 197 | } 198 | 199 | RCT_EXPORT_METHOD(openWXApp:(RCTPromiseResolveBlock)resolve 200 | rejecter:(RCTPromiseRejectBlock)reject) 201 | { 202 | BOOL open = [WXApi openWXApp]; 203 | if (open) { 204 | resolve([NSNull null]); 205 | } 206 | else { 207 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 208 | } 209 | } 210 | 211 | RCT_EXPORT_METHOD(openMiniprogram:(NSDictionary *)data 212 | resolver:(RCTPromiseResolveBlock)resolve 213 | rejecter:(RCTPromiseRejectBlock)reject) 214 | { 215 | WXLaunchMiniProgramReq *launchMiniProgramReq = [WXLaunchMiniProgramReq object]; 216 | launchMiniProgramReq.userName = data[@"userName"]; 217 | launchMiniProgramReq.path = data[@"path"]; 218 | if (![data objectForKey:@"miniprogramType"]) { 219 | launchMiniProgramReq.miniProgramType = WXMiniProgramTypeRelease; 220 | } 221 | else { 222 | launchMiniProgramReq.miniProgramType = [data[@"miniprogramType"] integerValue]; 223 | } 224 | 225 | [WXApi sendReq:launchMiniProgramReq completion:^(BOOL success) { 226 | if (success) { 227 | resolve([NSNull null]); 228 | } 229 | else { 230 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 231 | } 232 | }]; 233 | } 234 | 235 | RCT_EXPORT_METHOD(sendAuthRequest:(NSString *)scope 236 | state:(NSString *)state 237 | resolver:(RCTPromiseResolveBlock)resolve 238 | rejecter:(RCTPromiseRejectBlock)reject) 239 | { 240 | if (!instanceManager) { 241 | reject(getWXCommonErrorCode(), NOT_REGISTERED, nil); 242 | return; 243 | } 244 | SendAuthReq *req = [[SendAuthReq alloc] init]; 245 | req.scope = scope; 246 | req.state = state; 247 | 248 | [WXApi sendAuthReq:req 249 | viewController:RCTKeyWindow().rootViewController 250 | delegate:instanceManager 251 | completion:^(BOOL success) { 252 | if (success) { 253 | resolve([NSNull null]); 254 | } 255 | else { 256 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 257 | } 258 | }]; 259 | } 260 | 261 | 262 | /// 微信签约支付 263 | /// @param callback callback 264 | RCT_EXPORT_METHOD(entrust:(NSDictionary *)data 265 | resolver:(RCTPromiseResolveBlock)resolve 266 | rejecter:(RCTPromiseRejectBlock)reject) 267 | { 268 | NSString *preEntrustWebId = [data objectForKey:@"preEntrustWebId"]; 269 | if (!preEntrustWebId) { 270 | reject(getWXCommonErrorCode(), @"entrust: The value of preEntrustWebId cannot be empty", nil); 271 | return; 272 | } 273 | WXOpenBusinessWebViewReq *req = [[WXOpenBusinessWebViewReq alloc] init]; 274 | req.businessType = 12; //固定值 275 | NSMutableDictionary *queryInfoDic = [NSMutableDictionary dictionary]; 276 | [queryInfoDic setObject:preEntrustWebId forKey:@"pre_entrustweb_id"]; 277 | req.queryInfoDic = queryInfoDic; 278 | 279 | [WXApi sendReq:req completion:^(BOOL success) { 280 | if (success) { 281 | resolve([NSNull null]); 282 | } 283 | else { 284 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 285 | } 286 | }]; 287 | } 288 | 289 | RCT_EXPORT_METHOD(shareText:(NSDictionary *)data 290 | resolver:(RCTPromiseResolveBlock)resolve 291 | rejecter:(RCTPromiseRejectBlock)reject) 292 | { 293 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 294 | req.bText = YES; 295 | req.text = [data objectForKey:@"text"]; 296 | req.scene = [data objectForKey:@"scene"] ? [[data objectForKey:@"scene"] intValue] : WXSceneSession; 297 | [WXApi sendReq:req completion:^(BOOL success) { 298 | if (success) { 299 | resolve([NSNull null]); 300 | } 301 | else { 302 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 303 | } 304 | }]; 305 | } 306 | 307 | RCT_EXPORT_METHOD(shareImage:(NSDictionary *)data 308 | resolver:(RCTPromiseResolveBlock)resolve 309 | rejecter:(RCTPromiseRejectBlock)reject) 310 | { 311 | [self createImageRequest:[data objectForKey:@"imageUrl"] callback:^(NSError *err, UIImage *image) { 312 | if (err) { 313 | reject(getWXCommonErrorCode(), @"the imageUrl download failed", nil); 314 | return; 315 | } 316 | NSData *imageData = UIImageJPEGRepresentation(image, 1); 317 | WXImageObject *imageObject = [WXImageObject object]; 318 | imageObject.imageData = imageData; 319 | 320 | WXMediaMessage *message = [WXMediaMessage message]; 321 | message.mediaObject = imageObject; 322 | message.title = data[@"title"]; 323 | message.description = data[@"description"]; 324 | 325 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 326 | req.bText = NO; 327 | req.message = message; 328 | req.scene = [data[@"scene"] intValue]; 329 | [WXApi sendReq:req completion:^(BOOL success) { 330 | if (success) { 331 | resolve([NSNull null]); 332 | } 333 | else { 334 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 335 | } 336 | }]; 337 | }]; 338 | } 339 | 340 | RCT_EXPORT_METHOD(shareMusic:(NSDictionary *)data 341 | resolver:(RCTPromiseResolveBlock)resolve 342 | rejecter:(RCTPromiseRejectBlock)reject) 343 | { 344 | [self createImageRequest:[data objectForKey:@"thumbImageUrl"] callback:^(NSError *err, UIImage *image) { 345 | if (err) { 346 | reject(getWXCommonErrorCode(), @"the imageUrl download failed", nil); 347 | return; 348 | } 349 | WXMusicObject *media = [WXMusicObject object]; 350 | media.musicUrl = data[@"musicUrl"]; 351 | media.musicLowBandUrl = data[@"musicLowBandUrl"]; 352 | media.musicDataUrl = data[@"musicDataUrl"]; 353 | media.musicLowBandDataUrl = data[@"musicLowBandDataUrl"]; 354 | 355 | WXMediaMessage *message = [WXMediaMessage message]; 356 | message.title = data[@"title"]; 357 | message.description = data[@"description"]; 358 | [message setThumbImage:image]; 359 | message.mediaObject = media; 360 | 361 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 362 | req.bText = NO; 363 | req.message = message; 364 | req.scene = data[@"scene"] ? [data[@"scene"] intValue] : WXSceneSession; 365 | [WXApi sendReq:req completion:^(BOOL success) { 366 | if (success) { 367 | resolve([NSNull null]); 368 | } 369 | else { 370 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 371 | } 372 | }]; 373 | }]; 374 | } 375 | 376 | RCT_EXPORT_METHOD(shareVideo:(NSDictionary *)data 377 | resolver:(RCTPromiseResolveBlock)resolve 378 | rejecter:(RCTPromiseRejectBlock)reject) 379 | { 380 | [self createImageRequest:[data objectForKey:@"thumbImageUrl"] callback:^(NSError *err, UIImage *image) { 381 | WXVideoObject *media = [WXVideoObject object]; 382 | media.videoUrl = data[@"videoUrl"]; 383 | media.videoLowBandUrl = data[@"videoLowBandUrl"]; 384 | 385 | WXMediaMessage *message = [WXMediaMessage message]; 386 | message.title = data[@"title"]; 387 | message.description = data[@"description"]; 388 | [message setThumbImage:image]; 389 | message.mediaObject = media; 390 | 391 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 392 | req.bText = NO; 393 | req.message = message; 394 | req.scene = data[@"scene"] ? [data[@"scene"] intValue] : WXSceneSession; 395 | [WXApi sendReq:req completion:^(BOOL success) { 396 | if (success) { 397 | resolve([NSNull null]); 398 | } 399 | else { 400 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 401 | } 402 | }]; 403 | }]; 404 | } 405 | 406 | RCT_EXPORT_METHOD(shareWebpage:(NSDictionary *)data 407 | resolver:(RCTPromiseResolveBlock)resolve 408 | rejecter:(RCTPromiseRejectBlock)reject) 409 | { 410 | NSString *imageURL = [data objectForKey:@"thumbImageUrl"]; 411 | [self createImageRequest:imageURL callback:^(NSError *err, UIImage *image) { 412 | WXWebpageObject *webpageObject = [WXWebpageObject object]; 413 | webpageObject.webpageUrl = [data objectForKey:@"webpageUrl"]; 414 | WXMediaMessage *message = [WXMediaMessage message]; 415 | message.title = [data objectForKey:@"title"]; 416 | message.description = [data objectForKey:@"description"]; 417 | [message setThumbImage:image]; 418 | message.mediaObject = webpageObject; 419 | 420 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 421 | req.bText = NO; 422 | req.message = message; 423 | req.scene = data[@"scene"] ? [data[@"scene"] intValue] : WXSceneSession; 424 | [WXApi sendReq:req completion:^(BOOL success) { 425 | if (success) { 426 | resolve([NSNull null]); 427 | } 428 | else { 429 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 430 | } 431 | }]; 432 | }]; 433 | } 434 | 435 | RCT_EXPORT_METHOD(shareFile:(NSDictionary *)data 436 | resolver:(RCTPromiseResolveBlock)resolve 437 | rejecter:(RCTPromiseRejectBlock)reject) 438 | { 439 | WXFileObject *media = [WXFileObject object]; 440 | media.fileData = [NSData dataWithContentsOfFile:data[@"filePath"]]; 441 | media.fileExtension = data[@"fileExtension"]; 442 | 443 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 444 | req.bText = NO; 445 | req.message = media; 446 | req.scene = data[@"scene"] ? [data[@"scene"] intValue] : WXSceneSession; 447 | [WXApi sendReq:req completion:^(BOOL success) { 448 | if (success) { 449 | resolve([NSNull null]); 450 | } 451 | else { 452 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 453 | } 454 | }]; 455 | } 456 | 457 | RCT_EXPORT_METHOD(shareMiniprogram:(NSDictionary *)data 458 | resolver:(RCTPromiseResolveBlock)resolve 459 | rejecter:(RCTPromiseRejectBlock)reject) 460 | { 461 | NSString *hdImageUrl = data[@"thumbImageUrl"] ? data[@"thumbImageUrl"] : @""; 462 | [self createImageRequest:hdImageUrl callback:^(NSError *err, UIImage *image) { 463 | NSDictionary *miniprogram = [data objectForKey:@"miniprogram"]; 464 | WXMiniProgramObject *object = [WXMiniProgramObject object]; 465 | object.userName = [miniprogram objectForKey:@"userName"]; 466 | object.path = [miniprogram objectForKey:@"path"]; 467 | // 压缩图片到小于128KB 468 | object.hdImageData = image ? [self compressImage:image toByte:131072] : nil; 469 | object.miniProgramType = [miniprogram objectForKey:@"miniprogramType"] ? [[miniprogram objectForKey:@"miniprogramType"] integerValue] : WXMiniProgramTypeRelease; 470 | object.webpageUrl = [data objectForKey:@"webpageUrl"]; 471 | 472 | WXMediaMessage *message = [WXMediaMessage message]; 473 | message.title = data[@"title"]; 474 | message.description = data[@"description"]; 475 | message.mediaObject = object; 476 | 477 | SendMessageToWXReq *req = [[SendMessageToWXReq alloc] init]; 478 | req.bText = NO; 479 | req.message = message; 480 | req.scene = data[@"scene"] ? [data[@"scene"] intValue] : WXSceneSession; 481 | 482 | [WXApi sendReq:req completion:^(BOOL success) { 483 | if (success) { 484 | resolve([NSNull null]); 485 | } 486 | else { 487 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 488 | } 489 | }]; 490 | }]; 491 | } 492 | 493 | RCT_EXPORT_METHOD(pay:(NSDictionary *)data 494 | resolver:(RCTPromiseResolveBlock)resolve 495 | rejecter:(RCTPromiseRejectBlock)reject) 496 | { 497 | PayReq *req = [[PayReq alloc] init]; 498 | req.partnerId = data[@"partnerId"]; 499 | req.prepayId = data[@"prepayId"]; 500 | req.nonceStr = data[@"nonceStr"]; 501 | req.timeStamp = [data[@"timeStamp"] unsignedIntValue]; 502 | req.package = data[@"package"]; 503 | req.sign = data[@"sign"]; 504 | 505 | [WXApi sendReq:req completion:^(BOOL success) { 506 | if (success) { 507 | resolve([NSNull null]); 508 | } 509 | else { 510 | reject(getWXCommonErrorCode(), INVOKE_FAILED, nil); 511 | } 512 | }]; 513 | } 514 | 515 | #pragma mark - wx callback 516 | 517 | - (void)onReq:(BaseReq*)req 518 | { 519 | if ([req isKindOfClass:[LaunchFromWXReq class]]) { 520 | LaunchFromWXReq *launchReq = (LaunchFromWXReq *)req; 521 | NSString *appParameter = launchReq.message.messageExt; 522 | NSMutableDictionary *body = [[NSMutableDictionary alloc] init]; 523 | body[@"type"] = @"LaunchFromWX.Req"; 524 | body[@"lang"] = launchReq.lang; 525 | body[@"country"] = launchReq.country; 526 | body[@"extInfo"] = appParameter; 527 | 528 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTLaunchFromWXEvent body:body]; 529 | } 530 | } 531 | 532 | - (void)onResp:(BaseResp*)resp 533 | { 534 | if ([resp isKindOfClass:[SendMessageToWXResp class]]) 535 | { 536 | SendMessageToWXResp *r = (SendMessageToWXResp *)resp; 537 | 538 | NSMutableDictionary *body = @{@"errCode":@(r.errCode)}.mutableCopy; 539 | body[@"errStr"] = r.errStr; 540 | body[@"lang"] = r.lang; 541 | body[@"country"] = r.country; 542 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTWXSendMessageEvent body:body]; 543 | } 544 | else if ([resp isKindOfClass:[SendAuthResp class]]) 545 | { 546 | SendAuthResp *r = (SendAuthResp *)resp; 547 | NSMutableDictionary *body = @{@"errCode":@(r.errCode)}.mutableCopy; 548 | body[@"errStr"] = r.errStr; 549 | body[@"state"] = r.state; 550 | body[@"lang"] = r.lang; 551 | body[@"country"] = r.country; 552 | body[@"code"] = r.code; 553 | 554 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTWXSendAuthEvent body:body]; 555 | } 556 | else if ([resp isKindOfClass:[WXLaunchMiniProgramResp class]]) 557 | { 558 | WXLaunchMiniProgramResp *r = (WXLaunchMiniProgramResp *)resp; 559 | NSMutableDictionary *body = @{@"errCode":@(r.errCode)}.mutableCopy; 560 | body[@"data"] = r.extMsg; 561 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTWXLaunchMiniprogramEvent body:body]; 562 | } 563 | else if ([resp isKindOfClass:[PayResp class]]) 564 | { 565 | PayResp *r = (PayResp *)resp; 566 | NSMutableDictionary *body = @{@"errCode":@(r.errCode)}.mutableCopy; 567 | body[@"errStr"] = r.errStr; 568 | body[@"returnKey"] = r.returnKey; 569 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTWXPayEvent body:body]; 570 | } 571 | else if ([resp isKindOfClass:[WXOpenBusinessWebViewResp class]]) { 572 | WXOpenBusinessWebViewResp *business = (WXOpenBusinessWebViewResp *)resp; 573 | NSMutableDictionary *body = @{@"errCode":@(business.errCode)}.mutableCopy; 574 | body[@"errStr"] = business.errStr; 575 | body[@"result"] = business.result; 576 | body[@"businessType"] = @(business.businessType); 577 | [self.bridge.eventDispatcher sendDeviceEventWithName:RCTWXOpenBusinessWebViewEvent body:body]; 578 | } 579 | } 580 | 581 | @end 582 | -------------------------------------------------------------------------------- /android/src/main/java/com/theweflex/react/WechatModule.java: -------------------------------------------------------------------------------- 1 | package com.theweflex.react; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.net.Uri; 7 | 8 | import com.facebook.common.executors.UiThreadImmediateExecutorService; 9 | import com.facebook.common.references.CloseableReference; 10 | import com.facebook.common.util.UriUtil; 11 | import com.facebook.datasource.DataSource; 12 | import com.facebook.drawee.backends.pipeline.Fresco; 13 | import com.facebook.imagepipeline.core.ImagePipeline; 14 | import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; 15 | import com.facebook.imagepipeline.image.CloseableImage; 16 | import com.facebook.imagepipeline.request.ImageRequest; 17 | import com.facebook.imagepipeline.request.ImageRequestBuilder; 18 | import com.facebook.react.bridge.Arguments; 19 | import com.facebook.react.bridge.Promise; 20 | import com.facebook.react.bridge.ReactApplicationContext; 21 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 22 | import com.facebook.react.bridge.ReactMethod; 23 | import com.facebook.react.bridge.ReadableMap; 24 | import com.facebook.react.bridge.WritableMap; 25 | import com.facebook.react.module.annotations.ReactModule; 26 | import com.facebook.react.modules.core.DeviceEventManagerModule; 27 | import com.tencent.mm.opensdk.constants.ConstantsAPI; 28 | import com.tencent.mm.opensdk.modelbase.BaseReq; 29 | import com.tencent.mm.opensdk.modelbase.BaseResp; 30 | import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram; 31 | import com.tencent.mm.opensdk.modelbiz.WXOpenBusinessWebview; 32 | import com.tencent.mm.opensdk.modelmsg.SendAuth; 33 | import com.tencent.mm.opensdk.modelmsg.SendMessageToWX; 34 | import com.tencent.mm.opensdk.modelmsg.ShowMessageFromWX; 35 | import com.tencent.mm.opensdk.modelmsg.WXAppExtendObject; 36 | import com.tencent.mm.opensdk.modelmsg.WXFileObject; 37 | import com.tencent.mm.opensdk.modelmsg.WXImageObject; 38 | import com.tencent.mm.opensdk.modelmsg.WXMediaMessage; 39 | import com.tencent.mm.opensdk.modelmsg.WXMiniProgramObject; 40 | import com.tencent.mm.opensdk.modelmsg.WXMusicObject; 41 | import com.tencent.mm.opensdk.modelmsg.WXTextObject; 42 | import com.tencent.mm.opensdk.modelmsg.WXVideoObject; 43 | import com.tencent.mm.opensdk.modelmsg.WXWebpageObject; 44 | import com.tencent.mm.opensdk.modelpay.PayReq; 45 | import com.tencent.mm.opensdk.modelpay.PayResp; 46 | import com.tencent.mm.opensdk.openapi.IWXAPI; 47 | import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler; 48 | import com.tencent.mm.opensdk.openapi.WXAPIFactory; 49 | 50 | import java.io.ByteArrayOutputStream; 51 | import java.util.ArrayList; 52 | import java.util.HashMap; 53 | import java.util.UUID; 54 | 55 | import javax.annotation.Nullable; 56 | 57 | @ReactModule(name = WechatModule.NAME) 58 | public class WechatModule extends ReactContextBaseJavaModule implements IWXAPIEventHandler { 59 | public final static String NAME = "Wechat"; 60 | private String appId; 61 | private IWXAPI api = null; 62 | private final static String NOT_REGISTERED = "registerApp required."; 63 | private final static String INVOKE_FAILED = "Wechat API invoke returns false."; 64 | private final static String INVALID_ARGUMENT = "invalid argument."; 65 | private final static int THUMB_SIZE = 32; // The size of thumb image in KB. 66 | private static Intent wechatIntent; 67 | 68 | private static byte[] bitmapToBytesArray(Bitmap bitmap, final boolean needRecycle) { 69 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 70 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); 71 | if (needRecycle) { 72 | bitmap.recycle(); 73 | } 74 | try { 75 | baos.close(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | return baos.toByteArray(); 80 | } 81 | 82 | /** 83 | * Compress the bitmap image 84 | */ 85 | private static byte[] bitmapResizeGetBytes(Bitmap image, int size, final boolean needRecycle) { 86 | // FIXME(little-snow-fox): 该算法存在效率问题,希望有"义士"可以进行优化 87 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 88 | // 质量压缩方法,这里100表示第一次不压缩,把压缩后的数据缓存到 baos 89 | image.compress(Bitmap.CompressFormat.JPEG, 10, baos); 90 | int options = 10; 91 | // 循环判断压缩后依然大于 32kb 则继续压缩 92 | while (baos.toByteArray().length / 1024 > size) { 93 | // 重置baos即清空baos 94 | baos.reset(); 95 | // 每次都减少1 96 | options += 1; 97 | // 这里压缩options%,把压缩后的数据存放到baos中 98 | image.compress(Bitmap.CompressFormat.JPEG, 10 / options * 10, baos); 99 | } 100 | if (needRecycle) 101 | image.recycle(); 102 | 103 | return baos.toByteArray(); 104 | } 105 | 106 | public WechatModule(ReactApplicationContext context) { 107 | super(context); 108 | } 109 | 110 | @Override 111 | public String getName() { 112 | return NAME; 113 | } 114 | 115 | /** 116 | * fix Native module WechatModule tried to override WechatModule for module name RCTWechat. 117 | * If this was your intention, return true from WechatModule#canOverrideExistingModule() bug 118 | * 119 | * @return 120 | */ 121 | public boolean canOverrideExistingModule() { 122 | return true; 123 | } 124 | 125 | private static ArrayList modules = new ArrayList<>(); 126 | 127 | @Override 128 | public void initialize() { 129 | super.initialize(); 130 | modules.add(this); 131 | } 132 | 133 | @Override 134 | public void onCatalystInstanceDestroy() { 135 | super.onCatalystInstanceDestroy(); 136 | if (api != null) { 137 | api = null; 138 | } 139 | modules.remove(this); 140 | } 141 | 142 | /** 143 | * @param intent 冷启动需要将intent信息缓存,等到微信注册好再处理intent数据 144 | */ 145 | public static void handleIntent(Intent intent) { 146 | wechatIntent = intent; 147 | for (WechatModule mod : modules) { 148 | if (mod.api != null) 149 | mod.api.handleIntent(wechatIntent, mod); 150 | } 151 | } 152 | 153 | @ReactMethod 154 | public void registerApp(String appid, String universalLink, Promise promise) { 155 | this.appId = appid; 156 | api = WXAPIFactory.createWXAPI(this.getReactApplicationContext().getBaseContext(), appid, true); 157 | boolean result = api.registerApp(appid); 158 | if (result) { 159 | promise.resolve(null); 160 | for (WechatModule mod : modules) { 161 | if (mod.api != null && wechatIntent != null) 162 | mod.api.handleIntent(wechatIntent, mod); 163 | } 164 | } else { 165 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", "register failed"); 166 | } 167 | } 168 | 169 | @ReactMethod 170 | public void isWXAppInstalled(Promise promise) { 171 | if (api == null) { 172 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", NOT_REGISTERED); 173 | return; 174 | } 175 | promise.resolve(api.isWXAppInstalled()); 176 | } 177 | 178 | @ReactMethod 179 | public void isWXAppSupportApi(Promise promise) { 180 | if (api == null) { 181 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", NOT_REGISTERED); 182 | return; 183 | } 184 | promise.resolve(api.getWXAppSupportAPI()); 185 | } 186 | 187 | @ReactMethod 188 | public void openWXApp(Promise promise) { 189 | if (api == null) { 190 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", NOT_REGISTERED); 191 | return; 192 | } 193 | boolean result = api.openWXApp(); 194 | if (result) { 195 | promise.resolve(null); 196 | } else { 197 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", "register failed"); 198 | } 199 | 200 | } 201 | 202 | @ReactMethod 203 | public void openMiniprogram(ReadableMap data, Promise promise) { 204 | if (api == null) { 205 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", NOT_REGISTERED); 206 | return; 207 | } 208 | WXLaunchMiniProgram.Req req = new WXLaunchMiniProgram.Req(); 209 | // 填小程序原始ID 210 | req.userName = data.getString("userName"); 211 | // 拉起小程序页面的可带参路径,不填默认拉起小程序首页 212 | req.path = data.getString("path"); 213 | // 可选打开开发版,体验版和正式版 214 | req.miniprogramType = data.getInt("miniprogramType"); 215 | boolean success = api.sendReq(req); 216 | if (!success) { 217 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", INVALID_ARGUMENT); 218 | } 219 | } 220 | 221 | @ReactMethod 222 | public void sendAuthRequest(String scope, String state, Promise promise) { 223 | if (api == null) { 224 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", NOT_REGISTERED); 225 | return; 226 | } 227 | SendAuth.Req req = new SendAuth.Req(); 228 | req.scope = scope; 229 | req.state = state; 230 | boolean result = api.sendReq(req); 231 | if (result) { 232 | promise.resolve(null); 233 | } else { 234 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", "send Auth Request failed"); 235 | } 236 | } 237 | 238 | private void sendShareRequest(final WXMediaMessage.IMediaObject media, final ReadableMap data, final Promise promise) { 239 | if (data.hasKey("thumbImageUrl")) { 240 | createImageRequest(Uri.parse(data.getString("thumbImageUrl")), new ImageCallback() { 241 | @Override 242 | public void invoke(@Nullable Bitmap thumb) { 243 | WechatModule.this.sendShareRequest(media, thumb, data, promise); 244 | } 245 | }); 246 | } else { 247 | this.sendShareRequest(media, null, data, promise); 248 | } 249 | } 250 | 251 | private void sendShareRequest(WXMediaMessage.IMediaObject media, Bitmap thumb, ReadableMap data, Promise promise) { 252 | WXMediaMessage message = new WXMediaMessage(); 253 | message.mediaObject = media; 254 | if (data.hasKey("title")) { 255 | message.title = data.getString("title"); 256 | } 257 | if (data.hasKey("description")) { 258 | message.description = data.getString("description"); 259 | } 260 | if (data.hasKey("mediaTagName")) { 261 | message.mediaTagName = data.getString("mediaTagName"); 262 | } 263 | if (data.hasKey("messageAction")) { 264 | message.messageAction = data.getString("messageAction"); 265 | } 266 | if (data.hasKey("messageExt")) { 267 | message.messageExt = data.getString("messageExt"); 268 | } 269 | if (thumb != null) { 270 | byte[] thumbData = bitmapToBytesArray(thumb, false); 271 | if (thumbData.length / 1024 > THUMB_SIZE) { 272 | message.thumbData = bitmapResizeGetBytes(thumb, THUMB_SIZE, true); 273 | } else { 274 | message.thumbData = thumbData; 275 | thumb.recycle(); 276 | } 277 | } 278 | 279 | SendMessageToWX.Req req = new SendMessageToWX.Req(); 280 | req.scene = data.hasKey("scene") ? data.getInt("scene") : SendMessageToWX.Req.WXSceneSession; 281 | req.transaction = UUID.randomUUID().toString(); 282 | req.message = message; 283 | boolean result = api.sendReq(req); 284 | if (result) { 285 | promise.resolve(null); 286 | } else { 287 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", "sendReq failed"); 288 | } 289 | } 290 | 291 | /** 292 | * Share the plain text 293 | * 294 | * @param data 295 | * @param promise 296 | */ 297 | @ReactMethod 298 | public void shareText(ReadableMap data, Promise promise) { 299 | WXTextObject media = new WXTextObject(); 300 | media.text = data.getString("text"); 301 | this.sendShareRequest(media, null, data, promise); 302 | } 303 | 304 | /** 305 | * 分享图片 306 | * 307 | * @param data 308 | * @param promise 309 | */ 310 | @ReactMethod 311 | public void shareImage(final ReadableMap data, final Promise promise) { 312 | Uri imgUrl; 313 | try { 314 | imgUrl = Uri.parse(data.getString("imageUrl")); 315 | if (imgUrl.getScheme() == null) { 316 | // handle static resource if no schema is provided. 317 | // todo Drawable TYPE 318 | imgUrl = getResourceDrawableURI(getReactApplicationContext(), ""); 319 | } 320 | } catch (Exception ex) { 321 | imgUrl = null; 322 | } 323 | 324 | if (imgUrl == null) { 325 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", "imgUrl is null"); 326 | return; 327 | } 328 | createImageRequest(imgUrl, new ImageCallback() { 329 | @Override 330 | public void invoke(@Nullable Bitmap image) { 331 | WXImageObject media = new WXImageObject(image); 332 | WechatModule.this.sendShareRequest(media, image/* as thumb */, data, promise); 333 | } 334 | }); 335 | } 336 | 337 | /** 338 | * 分享音乐 339 | * 340 | * @param data 341 | * @param promise 342 | */ 343 | @ReactMethod 344 | public void shareMusic(final ReadableMap data, final Promise promise) { 345 | WXMusicObject media = new WXMusicObject(); 346 | media.musicUrl = data.hasKey("musicUrl") ? data.getString("musicUrl") : null; 347 | media.musicLowBandUrl = data.hasKey("musicLowBandUrl") ? data.getString("musicLowBandUrl") : null; 348 | media.musicDataUrl = data.hasKey("musicDataUrl") ? data.getString("musicDataUrl") : null; 349 | media.musicUrl = data.hasKey("musicUrl") ? data.getString("musicUrl") : null; 350 | media.musicLowBandDataUrl = data.hasKey("musicLowBandDataUrl") ? data.getString("musicLowBandDataUrl") : null; 351 | this.sendShareRequest(media, data, promise); 352 | } 353 | 354 | /** 355 | * 分享视频 356 | * 357 | * @param data 358 | * @param promise 359 | */ 360 | @ReactMethod 361 | public void shareVideo(final ReadableMap data, final Promise promise) { 362 | WXVideoObject media = new WXVideoObject(); 363 | media.videoUrl = data.hasKey("videoUrl") ? data.getString("videoUrl") : null; 364 | media.videoLowBandUrl = data.hasKey("videoLowBandUrl") ? data.getString("videoLowBandUrl") : null; 365 | this.sendShareRequest(media, data, promise); 366 | } 367 | 368 | /** 369 | * 分享网页 370 | * 371 | * @param data 372 | * @param promise 373 | */ 374 | @ReactMethod 375 | public void shareWebpage(final ReadableMap data, final Promise promise) { 376 | WXWebpageObject media = new WXWebpageObject(); 377 | media.webpageUrl = data.hasKey("webpageUrl") ? data.getString("webpageUrl") : null; 378 | this.sendShareRequest(media, data, promise); 379 | } 380 | 381 | /** 382 | * 分享网页 383 | * 384 | * @param data 385 | * @param promise 386 | */ 387 | @ReactMethod 388 | public void shareFile(final ReadableMap data, final Promise promise) { 389 | WXFileObject media = new WXFileObject(data.getString("filePath")); 390 | this.sendShareRequest(media, data, promise); 391 | } 392 | 393 | /** 394 | * 分享小程序 395 | * 396 | * @param data 397 | * @param promise 398 | */ 399 | @ReactMethod 400 | public void shareMiniprogram(final ReadableMap data, final Promise promise) { 401 | WXMiniProgramObject media = new WXMiniProgramObject(); 402 | ReadableMap miniprogram = data.getMap("miniprogram"); 403 | media.webpageUrl = data.hasKey("webpageUrl") ? data.getString("webpageUrl") : null; 404 | media.miniprogramType = miniprogram.hasKey("miniprogramType") ? miniprogram.getInt("miniprogramType") : WXMiniProgramObject.MINIPTOGRAM_TYPE_RELEASE; 405 | media.userName = miniprogram.hasKey("userName") ? miniprogram.getString("userName") : null; 406 | media.path = miniprogram.hasKey("path") ? miniprogram.getString("path") : null; 407 | this.sendShareRequest(media, data, promise); 408 | 409 | } 410 | 411 | @ReactMethod 412 | public void pay(ReadableMap data, Promise promise) { 413 | PayReq payReq = new PayReq(); 414 | if (data.hasKey("partnerId")) { 415 | payReq.partnerId = data.getString("partnerId"); 416 | } 417 | if (data.hasKey("prepayId")) { 418 | payReq.prepayId = data.getString("prepayId"); 419 | } 420 | if (data.hasKey("nonceStr")) { 421 | payReq.nonceStr = data.getString("nonceStr"); 422 | } 423 | if (data.hasKey("timeStamp")) { 424 | payReq.timeStamp = data.getString("timeStamp"); 425 | } 426 | if (data.hasKey("sign")) { 427 | payReq.sign = data.getString("sign"); 428 | } 429 | if (data.hasKey("package")) { 430 | payReq.packageValue = data.getString("package"); 431 | } 432 | if (data.hasKey("extData")) { 433 | payReq.extData = data.getString("extData"); 434 | } 435 | payReq.appId = appId; 436 | boolean result = api.sendReq(payReq); 437 | if (result) { 438 | promise.resolve(null); 439 | } else { 440 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", INVOKE_FAILED); 441 | } 442 | } 443 | 444 | @ReactMethod 445 | public void entrust(final ReadableMap data, final Promise promise) { 446 | if (!data.hasKey("preEntrustWebId")) { 447 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", INVALID_ARGUMENT); 448 | return; 449 | } 450 | 451 | WXOpenBusinessWebview.Req req = new WXOpenBusinessWebview.Req(); 452 | req.businessType = 12;//固定值 453 | HashMap queryInfo = new HashMap<>(); 454 | queryInfo.put("pre_entrustweb_id", data.getString("preEntrustWebId")); 455 | req.queryInfo = queryInfo; 456 | boolean result = api.sendReq(req); 457 | if (result) { 458 | promise.resolve(null); 459 | } else { 460 | promise.reject(BaseResp.ErrCode.ERR_COMM + "", INVOKE_FAILED); 461 | } 462 | } 463 | 464 | private static void createImageRequest(Uri uri, final ImageCallback imageCallback) { 465 | BaseBitmapDataSubscriber dataSubscriber = new BaseBitmapDataSubscriber() { 466 | @Override 467 | protected void onNewResultImpl(Bitmap bitmap) { 468 | if (bitmap != null) { 469 | if (bitmap.getConfig() != null) { 470 | bitmap = bitmap.copy(bitmap.getConfig(), true); 471 | imageCallback.invoke(bitmap); 472 | } else { 473 | bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true); 474 | imageCallback.invoke(bitmap); 475 | } 476 | } else { 477 | imageCallback.invoke(null); 478 | } 479 | } 480 | 481 | @Override 482 | protected void onFailureImpl(DataSource> dataSource) { 483 | imageCallback.invoke(null); 484 | } 485 | }; 486 | 487 | ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri); 488 | // if (resizeOptions != null) { 489 | // builder = builder.setResizeOptions(resizeOptions); 490 | // } 491 | ImageRequest imageRequest = builder.build(); 492 | ImagePipeline imagePipeline = Fresco.getImagePipeline(); 493 | DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, null); 494 | dataSource.subscribe(dataSubscriber, UiThreadImmediateExecutorService.getInstance()); 495 | } 496 | 497 | private static Uri getResourceDrawableURI(Context ctx, String name) { 498 | if (name == null || name.isEmpty()) { 499 | return null; 500 | } 501 | 502 | // FIXME(Yorkie): what's this for? 503 | name = name.toLowerCase().replace("-", "_"); 504 | int id = ctx.getResources().getIdentifier(name, "drawable", ctx.getPackageName()); 505 | if (id == 0) { 506 | return null; 507 | } else { 508 | return new Uri.Builder() 509 | .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) 510 | .path(String.valueOf(id)) 511 | .build(); 512 | } 513 | } 514 | 515 | @Override 516 | public void onReq(BaseReq req) { 517 | switch (req.getType()) { 518 | case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX: 519 | goToShowMsg((ShowMessageFromWX.Req) req); 520 | break; 521 | default: 522 | break; 523 | } 524 | } 525 | 526 | private void goToShowMsg(ShowMessageFromWX.Req showReq) { 527 | WXMediaMessage wxMsg = showReq.message; 528 | WXAppExtendObject obj = (WXAppExtendObject) wxMsg.mediaObject; 529 | WritableMap map = Arguments.createMap(); 530 | map.putString("country", showReq.country); 531 | map.putString("lang", showReq.lang); 532 | map.putString("extInfo", obj.extInfo); 533 | this.getReactApplicationContext() 534 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 535 | .emit("LaunchFromWX.Req", map); 536 | } 537 | 538 | @Override 539 | public void onResp(BaseResp baseResp) { 540 | WritableMap map = Arguments.createMap(); 541 | String emitType = null; 542 | map.putInt("errCode", baseResp.errCode); 543 | map.putString("errStr", baseResp.errStr); 544 | map.putString("openId", baseResp.openId); 545 | map.putString("transaction", baseResp.transaction); 546 | 547 | if (baseResp instanceof SendAuth.Resp) { 548 | SendAuth.Resp resp = (SendAuth.Resp) (baseResp); 549 | emitType = "SendAuth.Resp"; 550 | map.putString("code", resp.code); 551 | map.putString("state", resp.state); 552 | map.putString("url", resp.url); 553 | map.putString("lang", resp.lang); 554 | map.putString("country", resp.country); 555 | } else if (baseResp instanceof SendMessageToWX.Resp) { 556 | SendMessageToWX.Resp resp = (SendMessageToWX.Resp) (baseResp); 557 | emitType = "SendMessageToWX.Resp"; 558 | } else if (baseResp instanceof WXOpenBusinessWebview.Resp) { 559 | emitType = "WXOpenBusinessWebview.Resp"; 560 | map.putInt("businessType", ((WXOpenBusinessWebview.Resp) baseResp).businessType); 561 | map.putString("resultInfo", ((WXOpenBusinessWebview.Resp) baseResp).resultInfo); 562 | } else if (baseResp.getType() == ConstantsAPI.COMMAND_LAUNCH_WX_MINIPROGRAM) { 563 | WXLaunchMiniProgram.Resp resp = (WXLaunchMiniProgram.Resp) (baseResp); 564 | emitType = "LaunchMiniprogram.Resp"; 565 | map.putString("data", resp.extMsg); 566 | } else if (baseResp instanceof PayResp) { 567 | PayResp resp = (PayResp) (baseResp); 568 | emitType = "PayReq.Resp"; 569 | map.putString("returnKey", resp.returnKey); 570 | } 571 | if (emitType != null) { 572 | this.getReactApplicationContext() 573 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 574 | .emit(emitType, map); 575 | } 576 | } 577 | 578 | private interface ImageCallback { 579 | void invoke(@Nullable Bitmap bitmap); 580 | } 581 | 582 | private interface MediaObjectCallback { 583 | void invoke(@Nullable WXMediaMessage.IMediaObject mediaObject); 584 | } 585 | 586 | } 587 | --------------------------------------------------------------------------------