├── www └── smsRetriever.js ├── package.json ├── plugin.xml ├── readme.md └── src └── android └── com └── codingsans └── ionic └── smsRetriever └── AndroidSmsRetriever.java /www/smsRetriever.js: -------------------------------------------------------------------------------- 1 | var SmsRetrieverLoader = function(require, exports, module) { 2 | var exec = require('cordova/exec'); 3 | 4 | function SmsRetriever() {} 5 | 6 | SmsRetriever.prototype.startWatching = function(success, failure) { 7 | exec(success, failure, 'AndroidSmsRetriever', 'start', []); 8 | }; 9 | 10 | SmsRetriever.prototype.getAppHash = function (success, failure) { 11 | exec(success, failure, 'AndroidSmsRetriever', 'hash', []); 12 | }; 13 | var smsRetriever = new SmsRetriever(); 14 | module.exports = smsRetriever; 15 | }; 16 | 17 | SmsRetrieverLoader(require, exports, module); 18 | 19 | cordova.define("cordova/plugin/SmsRetriever", SmsRetrieverLoader); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-sms-retriever-manager", 3 | "version": "1.0.5", 4 | "description": "Easily retrieve SMS messages using the Google SMS Retriever API with our cross-platform plugin designed for Ionic/Cordova. This powerful tool streamlines the process and is specifically available for Android devices.", 5 | "cordova": { 6 | "id": "cordova-plugin-sms-retriever-manager", 7 | "platforms": [ 8 | "android" 9 | ] 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hanatharesh2712/ionic-native-sms-retriever-plugin-master.git" 14 | }, 15 | "keywords": [ 16 | "cordova", 17 | "phonegap", 18 | "sms", 19 | "ecosystem:cordova", 20 | "cordova-android" 21 | ], 22 | "engines": [ 23 | { 24 | "name": "cordova", 25 | "version": ">=3.0.0" 26 | } 27 | ], 28 | "author": "Haresh Hanat", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/hanatharesh2712/ionic-native-sms-retriever-plugin-master/issues" 32 | }, 33 | "homepage": "https://github.com/hanatharesh2712/ionic-native-sms-retriever-plugin-master" 34 | } 35 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | Cordova SMS Retriver Plugin 5 | Easily retrieve SMS messages using the Google SMS Retriever API with our cross-platform plugin designed for Ionic/Cordova. This powerful tool streamlines the process and is specifically available for Android devices. 6 | MIT 7 | ionic, ionic3, cordova, cordova-plugin, phonegap-plugin, ionic-framework, ionic-cordova, sms, sms-verificationm automatic SMS receive 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ionic-native-sms-retriever-plugin-master 2 | # Cordova SMS retriver Plugin 3 | 4 | Cross-platform plugin for Cordova / PhoneGap to to easily retrive SMS for your APP without need permission of SMS_READ. Available for **Android**. 5 | 6 | ## Installing the plugin 7 | 8 | Using the Cordova CLI run: 9 | 10 | ``` 11 | ionic cordova plugin add cordova-plugin-sms-retriever-manager 12 | npm install @ionic-native/sms-retriever 13 | ``` 14 | 15 | It is also possible to install via repo url directly (unstable), run : 16 | 17 | ```sh 18 | ionic cordova plugin add https://github.com/hanatharesh2712/ionic-native-sms-retriever-plugin-master.git 19 | ``` 20 | You can find working Demo for Cordova here: https://github.com/hanatharesh2712/automatic-sms-cordova/tree/main/hello 21 | ## Using the plugin 22 | HTML 23 | 24 | ```html 25 | 26 | 27 | ``` 28 | 29 | Javascript 30 | 31 | ```js 32 | document.querySelector('.hashCode').addEventListener('click', this.getAppHash); 33 | document.querySelector('.startWatching').addEventListener('click', this.retriveSMS); 34 | 35 | var app = { 36 | getAppHash: function() { 37 | window['cordova']['plugins']['smsRetriever']['getAppHash']( 38 | (result) => { 39 | // Once you get this hash code of your app. Please remove this code. 40 | alert(result); 41 | console.log('Hash', result); 42 | }, 43 | (err) => { 44 | console.log(err); 45 | }); 46 | }, 47 | 48 | retriveSMS: function() { 49 | window['cordova']['plugins']['smsRetriever']['startWatching']( 50 | // the first callback is the success callback. We got back the native code’s result here. 51 | (result) => { 52 | alert(result.Message); 53 | console.log('Message', result); 54 | }, 55 | // the second is the error callback where we get back the errors 56 | (err) => { 57 | console.log(err); 58 | }); 59 | } 60 | }; 61 | ``` 62 | You can find working Demo for Ionic 4 here: https://github.com/hanatharesh2712/sms-plugin-test 63 | 64 | Typescript (Ionic 4) 65 | ```typescript 66 | import { SmsRetriever } from '@ionic-native/sms-retriever/ngx'; 67 | 68 | constructor(private smsRetriever: SmsRetriever) { } 69 | 70 | ... 71 | 72 | // This function is to get hash string of APP. 73 | // * @return {Promise} Returns a promise that resolves when successfully generate hash of APP. 74 | this.smsRetriever.getAppHash() 75 | .then((res: any) => console.log(res)) 76 | .catch((error: any) => console.error(error)); 77 | 78 | // * This function start wathching message arrive event and retrive message text. 79 | // * @return {Promise} Returns a promise that resolves when retrives SMS text or TIMEOUT after 5 min. 80 | this.smsRetriever.startWatching() 81 | .then((res: any) => console.log(res)) 82 | .catch((error: any) => console.error(error)); 83 | ``` 84 | 85 | You can find working Demo for Ionic 3 here: https://github.com/hanatharesh2712/sms-plugin-test-ionic-3 86 | 87 | Typescript (Ionic 3) 88 | ```typescript 89 | 90 | import { SmsRetriever } from '@ionic-native/sms-retriever/ngx'; 91 | var smsRetriever = window['cordova']['plugins']['smsRetriever']; 92 | 93 | public smsTextmessage: string = ''; 94 | public appHashString: string = ''; 95 | constructor(private smsRetriever: SmsRetriever) { } 96 | 97 | ... 98 | 99 | getHashCode() { 100 | smsRetriever['getAppHash']( 101 | (res) => { 102 | this.appHashString = res; 103 | console.log(res); 104 | }, (err) => { 105 | console.warn(err); 106 | } 107 | ); 108 | } 109 | 110 | getSMS() { 111 | smsRetriever['startWatching']( 112 | (res) => { 113 | this.smsTextmessage = res.Message; 114 | console.log(res); 115 | }, (err) => { 116 | console.warn(err); 117 | } 118 | ); 119 | } 120 | ``` 121 | 122 | Flow to test: 123 | [![flow](https://raw.githubusercontent.com/hanatharesh2712/automatic-sms-cordova/main/hello/res/ref-images/sms%20plugin%20demo.png)](https://raw.githubusercontent.com/hanatharesh2712/automatic-sms-cordova/main/hello/res/ref-images/sms%20plugin%20demo.png) 124 | 125 | You need to send your application hash in SMS when you are sending from your backend. to generate the hash of your application read this: https://developers.google.com/identity/sms-retriever/verify 126 | 127 | To get your application hash code: 128 | 129 | * Without the correct hash, your app won't recieve the message callback. This only needs to be 130 | * generated once per app and stored. Then you can remove this function from your code. 131 | 132 | BUILD FAILED 133 | 134 | The problem is that you need to make sure that you set the target to android-19 or later in your ./platforms/android/project.properties file like this: 135 | 136 | # Project target. 137 | target=android-19 138 | 139 | 140 | ## Donations 141 | 142 | If your app is successful or if you are working for a company, please consider donating some beer money :beer:: 143 | 144 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YP2LMRCJMGTNJ&source=url) 145 | 146 | Keep in mind that I am maintaining this repository on my free time so thank you for considering a donation. :+1: 147 | 148 | 149 | ## Contributing 150 | 151 | I believe that everything is working, feel free to put in an issue or to fork and make pull requests if you want to add a new feature. 152 | 153 | Things you can fix: 154 | * Allow for null number to be passed in 155 | Right now, it breaks when a null value is passed in for a number, but it works if it's a blank string, and allows the user to pick the number 156 | It should automatically convert a null value to an empty string 157 | 158 | Thanks for considering contributing to this project. 159 | 160 | ### Finding something to do 161 | 162 | Ask, or pick an issue and comment on it announcing your desire to work on it. Ideally wait until we assign it to you to minimize work duplication. 163 | 164 | ### Reporting an issue 165 | 166 | - Search existing issues before raising a new one. 167 | 168 | - Include as much detail as possible. 169 | 170 | ### Pull requests 171 | 172 | - Make it clear in the issue tracker what you are working on, so that someone else doesn't duplicate the work. 173 | 174 | - Use a feature branch, not master. 175 | 176 | - Rebase your feature branch onto origin/master before raising the PR. 177 | 178 | - Keep up to date with changes in master so your PR is easy to merge. 179 | 180 | - Be descriptive in your PR message: what is it for, why is it needed, etc. 181 | 182 | - Make sure the tests pass 183 | 184 | - Squash related commits as much as possible. 185 | 186 | ### Coding style 187 | 188 | - Try to match the existing indent style. 189 | 190 | - Don't mix platform-specific stuff into the main code. 191 | 192 | 193 | 194 | 195 | ## History 196 | 197 | 198 | ## License 199 | 200 | The MIT License 201 | 202 | Permission is hereby granted, free of charge, to any person obtaining a copy of 203 | this software and associated documentation files (the "Software"), to deal in 204 | the Software without restriction, including without limitation the rights to 205 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 206 | the Software, and to permit persons to whom the Software is furnished to do so, 207 | subject to the following conditions: 208 | 209 | The above copyright notice and this permission notice shall be included in all 210 | copies or substantial portions of the Software. 211 | 212 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 213 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 214 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 215 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 216 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 217 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 218 | -------------------------------------------------------------------------------- /src/android/com/codingsans/ionic/smsRetriever/AndroidSmsRetriever.java: -------------------------------------------------------------------------------- 1 | package com.codingsans.ionic.smsRetriever; 2 | 3 | import org.apache.cordova.CallbackContext; 4 | import org.apache.cordova.CordovaInterface; 5 | import org.apache.cordova.CordovaWebView; 6 | import org.apache.cordova.CordovaPlugin; 7 | import org.apache.cordova.PluginResult; 8 | import org.apache.cordova.LOG; 9 | 10 | import android.content.BroadcastReceiver; 11 | import android.content.Intent; 12 | import android.content.IntentFilter; 13 | import android.os.Bundle; 14 | import android.os.Build; 15 | import android.util.Log; 16 | import android.widget.Toast; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONObject; 20 | import org.json.JSONException; 21 | 22 | import android.content.Context; 23 | import com.google.android.gms.auth.api.phone.SmsRetriever; 24 | import com.google.android.gms.auth.api.phone.SmsRetrieverClient; 25 | import com.google.android.gms.common.api.Status; 26 | import com.google.android.gms.tasks.OnFailureListener; 27 | import com.google.android.gms.tasks.OnSuccessListener; 28 | import com.google.android.gms.common.api.CommonStatusCodes; 29 | import com.google.android.gms.tasks.Task; 30 | 31 | import java.util.ArrayList; 32 | 33 | import static com.google.android.gms.common.api.CommonStatusCodes.*; 34 | import static android.content.Context.RECEIVER_EXPORTED; 35 | 36 | import android.content.ContextWrapper; 37 | import android.content.pm.PackageManager; 38 | import android.content.pm.Signature; 39 | import android.util.Base64; 40 | 41 | import java.nio.charset.StandardCharsets; 42 | import java.security.MessageDigest; 43 | import java.security.NoSuchAlgorithmException; 44 | import java.util.Arrays; 45 | 46 | public class AndroidSmsRetriever extends CordovaPlugin { 47 | 48 | private SmsRetrieverClient smsRetrieverClient; 49 | //public static final int MAX_TIMEOUT = 300000; // 5 mins in millis 50 | private static final String TAG = AndroidSmsRetriever.class.getSimpleName(); 51 | 52 | private static final String HASH_TYPE = "SHA-256"; 53 | public static final int NUM_HASHED_BYTES = 9; 54 | public static final int NUM_BASE64_CHAR = 11; 55 | 56 | private CallbackContext callbackContext; 57 | private JSONObject data = new JSONObject(); 58 | 59 | @Override 60 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 61 | LOG.v(TAG, "SmsRetriever: initialization"); 62 | //Toast.makeText(this.cordova.getActivity().getApplicationContext(),"SmsRetriever: initialization", Toast.LENGTH_SHORT).show(); 63 | 64 | super.initialize(cordova, webView); 65 | 66 | // Get an instance of SmsRetrieverClient, used to start listening for a matching 67 | // SMS message. 68 | smsRetrieverClient = SmsRetriever.getClient(this.cordova.getActivity().getApplicationContext()); 69 | } 70 | 71 | /** 72 | * Get all the app signatures for the current package 73 | * @return 74 | */ 75 | public ArrayList getAppSignatures() { 76 | ArrayList appCodes = new ArrayList<>(); 77 | 78 | try { 79 | // Get all package signatures for the current package 80 | String packageName = cordova.getActivity().getApplicationContext().getPackageName(); 81 | PackageManager packageManager = cordova.getActivity().getApplicationContext().getPackageManager(); 82 | Signature[] signatures = packageManager.getPackageInfo(packageName, 83 | PackageManager.GET_SIGNATURES).signatures; 84 | 85 | // For each signature create a compatible hash 86 | for (Signature signature : signatures) { 87 | String hash = hash(packageName, signature.toCharsString()); 88 | if (hash != null) { 89 | appCodes.add(String.format("%s", hash)); 90 | } 91 | } 92 | } catch (PackageManager.NameNotFoundException e) { 93 | Log.e(TAG, "Unable to find package to obtain hash.", e); 94 | } 95 | return appCodes; 96 | } 97 | 98 | private static String hash(String packageName, String signature) { 99 | String appInfo = packageName + " " + signature; 100 | try { 101 | MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE); 102 | messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8)); 103 | byte[] hashSignature = messageDigest.digest(); 104 | 105 | // truncated into NUM_HASHED_BYTES 106 | hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES); 107 | // encode into Base64 108 | String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP); 109 | base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR); 110 | 111 | Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)); 112 | 113 | return base64Hash; 114 | } catch (NoSuchAlgorithmException e) { 115 | Log.e(TAG, "hash:NoSuchAlgorithm", e); 116 | } 117 | return null; 118 | } 119 | 120 | @Override 121 | public void onDestroy() { 122 | try { 123 | if (mMessageReceiver != null) { 124 | this.cordova.getActivity().getApplicationContext().unregisterReceiver(mMessageReceiver); 125 | mMessageReceiver = null; 126 | } 127 | } catch(IllegalArgumentException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | @Override 133 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 134 | this.callbackContext = callbackContext; 135 | 136 | LOG.v(TAG, "Executing action: " + action); 137 | //Toast.makeText(this.cordova.getActivity().getApplicationContext(),"Executing action: " + action, Toast.LENGTH_SHORT).show(); 138 | 139 | if ("start".equals(action)) { 140 | // Starts SmsRetriever, which waits for ONE matching SMS message until timeout 141 | // (5 minutes). The matching SMS message will be sent via a Broadcast Intent with 142 | // action SmsRetriever#SMS_RETRIEVED_ACTION. 143 | Task task = smsRetrieverClient.startSmsRetriever(); 144 | 145 | // Listen for success/failure of the start Task. If in a background thread, this 146 | // can be made blocking using Tasks.await(task, [timeout]); 147 | task.addOnSuccessListener(new OnSuccessListener() { 148 | @Override 149 | public void onSuccess(Void aVoid) { 150 | // Successfully started retriever, expect broadcast intent 151 | LOG.v(TAG, "Executing action: addOnSuccessListener"); 152 | //Toast.makeText(cordova.getActivity().getApplicationContext(),"Executing action: addOnSuccessListener", Toast.LENGTH_SHORT).show(); 153 | 154 | IntentFilter intentFilter = new IntentFilter(); 155 | intentFilter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION); 156 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 157 | cordova.getActivity().getApplicationContext().registerReceiver(mMessageReceiver, intentFilter, RECEIVER_EXPORTED); 158 | } else { 159 | cordova.getActivity().getApplicationContext().registerReceiver(mMessageReceiver, intentFilter); 160 | } 161 | 162 | } 163 | }); 164 | 165 | task.addOnFailureListener(new OnFailureListener() { 166 | @Override 167 | public void onFailure(Exception e) { 168 | // Failed to start retriever, inspect Exception for more details 169 | LOG.v(TAG, "Executing action: addOnFailureListener"); 170 | //Toast.makeText(cordova.getActivity().getApplicationContext(),"Executing action: addOnFailureListener", Toast.LENGTH_SHORT).show(); 171 | } 172 | }); 173 | 174 | PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); 175 | r.setKeepCallback(true); 176 | callbackContext.sendPluginResult(r); 177 | 178 | return true; 179 | 180 | } 181 | else if ("hash".equals(action)) { 182 | 183 | ArrayList strHashCodes = getAppSignatures(); 184 | 185 | if(strHashCodes.size() == 0 || strHashCodes.get(0) == null){ 186 | 187 | String err = "Unable to find package to obtain hash code of application"; 188 | PluginResult result = new PluginResult(PluginResult.Status.ERROR, err); 189 | callbackContext.sendPluginResult(result); 190 | 191 | } else { 192 | 193 | String strApplicationHash = strHashCodes.get(0); 194 | PluginResult result = new PluginResult(PluginResult.Status.OK, strApplicationHash); 195 | callbackContext.sendPluginResult(result); 196 | 197 | } 198 | } 199 | 200 | // Returning false results in a "MethodNotFound" error. 201 | return false; 202 | } 203 | 204 | // Our handler for received Intents. This will be called whenever an Intent 205 | // with an action named "custom-event-name" is broadcasted. 206 | private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { 207 | @Override 208 | public void onReceive(Context context, Intent intent) { 209 | 210 | if (intent.getAction().equals(SmsRetriever.SMS_RETRIEVED_ACTION)) { 211 | final Bundle extra = intent.getExtras(); 212 | if (extra != null && extra.containsKey(SmsRetriever.EXTRA_STATUS)) { 213 | Status status = (Status) extra.get(SmsRetriever.EXTRA_STATUS); 214 | switch (status.getStatusCode()) { 215 | case CommonStatusCodes.SUCCESS: 216 | final String message = extra.getString(SmsRetriever.EXTRA_SMS_MESSAGE); 217 | //if (!StringUtils.hasContent(message)) return; 218 | if(message == null) return; 219 | 220 | Log.d(TAG, message); 221 | 222 | data = new JSONObject(); 223 | try { 224 | data.put("Message",message); 225 | } catch(JSONException e) {} 226 | 227 | //Toast.makeText(cordova.getActivity().getApplicationContext(),"Message: "+ message, Toast.LENGTH_LONG).show(); 228 | PluginResult result = new PluginResult(PluginResult.Status.OK, data); 229 | callbackContext.sendPluginResult(result); 230 | 231 | break; 232 | case CommonStatusCodes.TIMEOUT: 233 | 234 | PluginResult resultTimeout = new PluginResult(PluginResult.Status.ERROR, "TIMEOUT"); 235 | callbackContext.sendPluginResult(resultTimeout); 236 | break; 237 | } 238 | } 239 | } 240 | }; 241 | }; 242 | } 243 | --------------------------------------------------------------------------------