@capawesome/capacitor-android-sms-retriever 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CapawesomeCapacitorAndroidSmsRetriever.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +206 -0
  5. package/android/build.gradle +62 -0
  6. package/android/src/main/AndroidManifest.xml +1 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/AndroidSmsRetriever.java +194 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/AndroidSmsRetrieverPlugin.java +153 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/CustomException.java +20 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/CustomExceptions.java +17 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/options/RetrieveSmsOptions.java +25 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/results/RequestPhoneNumberResult.java +23 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/results/RetrieveSmsResult.java +23 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/Callback.java +5 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/NonEmptyResultCallback.java +7 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/Result.java +7 -0
  17. package/android/src/main/res/.gitkeep +0 -0
  18. package/dist/docs.json +147 -0
  19. package/dist/esm/definitions.d.ts +105 -0
  20. package/dist/esm/definitions.js +37 -0
  21. package/dist/esm/definitions.js.map +1 -0
  22. package/dist/esm/index.d.ts +4 -0
  23. package/dist/esm/index.js +7 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/web.d.ts +6 -0
  26. package/dist/esm/web.js +10 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +61 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +64 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Plugin/AndroidSmsRetrieverPlugin.swift +24 -0
  33. package/ios/Plugin/Info.plist +24 -0
  34. package/package.json +91 -0
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapawesomeCapacitorAndroidSmsRetriever'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '15.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robin Genz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapawesomeCapacitorAndroidSmsRetriever",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CapawesomeCapacitorAndroidSmsRetriever",
10
+ targets: ["AndroidSmsRetrieverPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "AndroidSmsRetrieverPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Plugin"),
23
+ .testTarget(
24
+ name: "AndroidSmsRetrieverPluginTests",
25
+ dependencies: ["AndroidSmsRetrieverPlugin"],
26
+ path: "ios/PluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # @capawesome/capacitor-android-sms-retriever
2
+
3
+ Capacitor plugin for OTP autofill on Android via the SMS User Consent and Phone Number Hint APIs.
4
+
5
+ <div class="capawesome-z29o10a">
6
+ <a href="https://cloud.capawesome.io/" target="_blank">
7
+ <img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
8
+ </a>
9
+ </div>
10
+
11
+ ## Features
12
+
13
+ - 💬 **SMS User Consent**: Read an incoming verification SMS after a one-tap system consent dialog.
14
+ - 📞 **Phone Number Hint**: Prefill the user's phone number via the system bottom sheet.
15
+ - 🔒 **No SMS permissions**: Uses Play-policy-safe APIs that require no SMS permissions.
16
+ - 🔁 **Up-to-date**: Always supports the latest Capacitor version.
17
+
18
+ Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
19
+
20
+ ## Newsletter
21
+
22
+ Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/).
23
+
24
+ ## Compatibility
25
+
26
+ | Plugin Version | Capacitor Version | Status |
27
+ | -------------- | ----------------- | -------------- |
28
+ | 0.x.x | >=8.x.x | Active support |
29
+
30
+ ## Installation
31
+
32
+ You can use our **AI-Assisted Setup** to install the plugin.
33
+ Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command:
34
+
35
+ ```bash
36
+ npx skills add capawesome-team/skills --skill capacitor-plugins
37
+ ```
38
+
39
+ Then use the following prompt:
40
+
41
+ ```
42
+ Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-android-sms-retriever` plugin in my project.
43
+ ```
44
+
45
+ If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below:
46
+
47
+ ```bash
48
+ npm install @capawesome/capacitor-android-sms-retriever
49
+ npx cap sync
50
+ ```
51
+
52
+ This plugin is only available on **Android**. On iOS and Web, all methods reject as unimplemented (see [iOS](#ios) below).
53
+
54
+ ### Android
55
+
56
+ #### Variables
57
+
58
+ This plugin will use the following project variables (defined in your app's `variables.gradle` file):
59
+
60
+ - `$playServicesAuthVersion` version of `com.google.android.gms:play-services-auth` (default: `21.5.0`)
61
+ - `$playServicesAuthApiPhoneVersion` version of `com.google.android.gms:play-services-auth-api-phone` (default: `18.3.0`)
62
+
63
+ ### iOS
64
+
65
+ This plugin has **no iOS implementation** and you don't need one. iOS already autofills one-time codes from incoming SMS messages in `WKWebView` when the input field uses `autocomplete="one-time-code"`:
66
+
67
+ ```html
68
+ <input autocomplete="one-time-code" />
69
+ ```
70
+
71
+ All methods reject as unimplemented on iOS.
72
+
73
+ ## Configuration
74
+
75
+ No configuration required for this plugin.
76
+
77
+ ## Usage
78
+
79
+ ```typescript
80
+ import { AndroidSmsRetriever } from '@capawesome/capacitor-android-sms-retriever';
81
+
82
+ const requestPhoneNumber = async () => {
83
+ const { phoneNumber } = await AndroidSmsRetriever.requestPhoneNumber();
84
+ return phoneNumber;
85
+ };
86
+
87
+ const retrieveSms = async () => {
88
+ const { message } = await AndroidSmsRetriever.retrieveSms();
89
+ // Extract the one-time code from the message.
90
+ const code = message.match(/\d{6}/)?.[0];
91
+ return code;
92
+ };
93
+ ```
94
+
95
+ ## API
96
+
97
+ <docgen-index>
98
+
99
+ * [`requestPhoneNumber()`](#requestphonenumber)
100
+ * [`retrieveSms(...)`](#retrievesms)
101
+ * [Interfaces](#interfaces)
102
+
103
+ </docgen-index>
104
+
105
+ <docgen-api>
106
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
107
+
108
+ ### requestPhoneNumber()
109
+
110
+ ```typescript
111
+ requestPhoneNumber() => Promise<RequestPhoneNumberResult>
112
+ ```
113
+
114
+ Request the user's phone number via the Phone Number Hint API.
115
+
116
+ A system bottom sheet is displayed that lets the user pick one of the
117
+ phone numbers associated with the device. The selected phone number is
118
+ returned so it can be used to prefill a phone number input field.
119
+
120
+ Only available on Android.
121
+
122
+ **Returns:** <code>Promise&lt;<a href="#requestphonenumberresult">RequestPhoneNumberResult</a>&gt;</code>
123
+
124
+ **Since:** 0.1.0
125
+
126
+ --------------------
127
+
128
+
129
+ ### retrieveSms(...)
130
+
131
+ ```typescript
132
+ retrieveSms(options?: RetrieveSmsOptions | undefined) => Promise<RetrieveSmsResult>
133
+ ```
134
+
135
+ Retrieve an incoming verification SMS via the SMS User Consent API.
136
+
137
+ A system consent dialog is displayed when a matching SMS is received.
138
+ The promise resolves with the full message text once the user consents,
139
+ so the app can extract the one-time code itself.
140
+
141
+ The underlying broadcast waits up to 5 minutes for a matching SMS. If no
142
+ SMS is received within this time, the promise rejects with the error
143
+ code `TIMEOUT`.
144
+
145
+ Only available on Android.
146
+
147
+ | Param | Type |
148
+ | ------------- | ----------------------------------------------------------------- |
149
+ | **`options`** | <code><a href="#retrievesmsoptions">RetrieveSmsOptions</a></code> |
150
+
151
+ **Returns:** <code>Promise&lt;<a href="#retrievesmsresult">RetrieveSmsResult</a>&gt;</code>
152
+
153
+ **Since:** 0.1.0
154
+
155
+ --------------------
156
+
157
+
158
+ ### Interfaces
159
+
160
+
161
+ #### RequestPhoneNumberResult
162
+
163
+ | Prop | Type | Description | Since |
164
+ | ----------------- | ------------------- | -------------------------------------- | ----- |
165
+ | **`phoneNumber`** | <code>string</code> | The phone number selected by the user. | 0.1.0 |
166
+
167
+
168
+ #### RetrieveSmsResult
169
+
170
+ | Prop | Type | Description | Since |
171
+ | ------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------- | ----- |
172
+ | **`message`** | <code>string</code> | The full text of the retrieved SMS message. The app is responsible for extracting the one-time code from the message. | 0.1.0 |
173
+
174
+
175
+ #### RetrieveSmsOptions
176
+
177
+ | Prop | Type | Description | Since |
178
+ | ----------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------- | ----- |
179
+ | **`senderPhoneNumber`** | <code>string</code> | The phone number of the sender to filter incoming messages by. If not provided, the SMS from any sender is retrieved. | 0.1.0 |
180
+
181
+ </docgen-api>
182
+
183
+ ## Extracting the One-Time Code
184
+
185
+ The `retrieveSms(...)` method resolves with the full text of the incoming SMS message. Your app is responsible for extracting the one-time code from the message, for example with a regular expression:
186
+
187
+ ```typescript
188
+ const { message } = await AndroidSmsRetriever.retrieveSms();
189
+ const code = message.match(/\d{6}/)?.[0];
190
+ ```
191
+
192
+ For the SMS User Consent API to detect a message, the SMS must:
193
+
194
+ - contain a one-time code that the user sends back to your server to complete the verification,
195
+ - be no longer than 140 bytes,
196
+ - not originate from a phone number in the user's contacts.
197
+
198
+ The underlying broadcast waits up to 5 minutes for a matching SMS. If no message is received within this time, the promise rejects with the error code `TIMEOUT`.
199
+
200
+ ## Changelog
201
+
202
+ See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-sms-retriever/CHANGELOG.md).
203
+
204
+ ## License
205
+
206
+ See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-sms-retriever/LICENSE).
@@ -0,0 +1,62 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ playServicesAuthVersion = project.hasProperty('playServicesAuthVersion') ? rootProject.ext.playServicesAuthVersion : '21.5.0'
7
+ playServicesAuthApiPhoneVersion = project.hasProperty('playServicesAuthApiPhoneVersion') ? rootProject.ext.playServicesAuthApiPhoneVersion : '18.3.0'
8
+ }
9
+
10
+ buildscript {
11
+ repositories {
12
+ google()
13
+ mavenCentral()
14
+ }
15
+ dependencies {
16
+ classpath 'com.android.tools.build:gradle:8.13.0'
17
+ }
18
+ }
19
+
20
+ apply plugin: 'com.android.library'
21
+
22
+ android {
23
+ namespace = "io.capawesome.capacitorjs.plugins.androidsmsretriever"
24
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
25
+ defaultConfig {
26
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
27
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
28
+ versionCode 1
29
+ versionName "1.0"
30
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
31
+ }
32
+ buildTypes {
33
+ release {
34
+ minifyEnabled false
35
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
36
+ }
37
+ }
38
+ lintOptions {
39
+ abortOnError = false
40
+ }
41
+ compileOptions {
42
+ sourceCompatibility JavaVersion.VERSION_21
43
+ targetCompatibility JavaVersion.VERSION_21
44
+ }
45
+ }
46
+
47
+ repositories {
48
+ google()
49
+ mavenCentral()
50
+ }
51
+
52
+
53
+ dependencies {
54
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
55
+ implementation project(':capacitor-android')
56
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
57
+ implementation "com.google.android.gms:play-services-auth:$playServicesAuthVersion"
58
+ implementation "com.google.android.gms:play-services-auth-api-phone:$playServicesAuthApiPhoneVersion"
59
+ testImplementation "junit:junit:$junitVersion"
60
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
61
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
62
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"></manifest>
@@ -0,0 +1,194 @@
1
+ package io.capawesome.capacitorjs.plugins.androidsmsretriever;
2
+
3
+ import android.content.BroadcastReceiver;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.content.IntentFilter;
7
+ import android.os.Bundle;
8
+ import androidx.annotation.NonNull;
9
+ import androidx.annotation.Nullable;
10
+ import androidx.core.content.ContextCompat;
11
+ import com.google.android.gms.auth.api.identity.GetPhoneNumberHintIntentRequest;
12
+ import com.google.android.gms.auth.api.identity.Identity;
13
+ import com.google.android.gms.auth.api.phone.SmsRetriever;
14
+ import com.google.android.gms.common.ConnectionResult;
15
+ import com.google.android.gms.common.GoogleApiAvailability;
16
+ import com.google.android.gms.common.api.CommonStatusCodes;
17
+ import com.google.android.gms.common.api.Status;
18
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.CustomExceptions;
19
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.options.RetrieveSmsOptions;
20
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.results.RequestPhoneNumberResult;
21
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.results.RetrieveSmsResult;
22
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.interfaces.NonEmptyResultCallback;
23
+
24
+ public class AndroidSmsRetriever {
25
+
26
+ @NonNull
27
+ private final AndroidSmsRetrieverPlugin plugin;
28
+
29
+ @Nullable
30
+ private NonEmptyResultCallback<RequestPhoneNumberResult> pendingPhoneNumberCallback;
31
+
32
+ @Nullable
33
+ private NonEmptyResultCallback<RetrieveSmsResult> pendingRetrieveSmsCallback;
34
+
35
+ @Nullable
36
+ private BroadcastReceiver smsBroadcastReceiver;
37
+
38
+ public AndroidSmsRetriever(@NonNull AndroidSmsRetrieverPlugin plugin) {
39
+ this.plugin = plugin;
40
+ }
41
+
42
+ public void handleOnDestroy() {
43
+ unregisterSmsBroadcastReceiver();
44
+ pendingPhoneNumberCallback = null;
45
+ pendingRetrieveSmsCallback = null;
46
+ }
47
+
48
+ public void handlePhoneNumberCanceled() {
49
+ if (pendingPhoneNumberCallback == null) {
50
+ return;
51
+ }
52
+ NonEmptyResultCallback<RequestPhoneNumberResult> callback = pendingPhoneNumberCallback;
53
+ pendingPhoneNumberCallback = null;
54
+ callback.error(CustomExceptions.CANCELED);
55
+ }
56
+
57
+ public void handlePhoneNumberResult(@Nullable Intent data) {
58
+ if (pendingPhoneNumberCallback == null) {
59
+ return;
60
+ }
61
+ NonEmptyResultCallback<RequestPhoneNumberResult> callback = pendingPhoneNumberCallback;
62
+ pendingPhoneNumberCallback = null;
63
+ try {
64
+ String phoneNumber = Identity.getSignInClient(plugin.getActivity()).getPhoneNumberFromIntent(data);
65
+ callback.success(new RequestPhoneNumberResult(phoneNumber));
66
+ } catch (Exception exception) {
67
+ callback.error(CustomExceptions.PHONE_NUMBER_HINT_FAILED);
68
+ }
69
+ }
70
+
71
+ public void handleSmsConsentCanceled() {
72
+ rejectPendingRetrieveSms(CustomExceptions.USER_DENIED);
73
+ }
74
+
75
+ public void handleSmsConsentResult(@Nullable Intent data) {
76
+ if (pendingRetrieveSmsCallback == null) {
77
+ return;
78
+ }
79
+ NonEmptyResultCallback<RetrieveSmsResult> callback = pendingRetrieveSmsCallback;
80
+ pendingRetrieveSmsCallback = null;
81
+ String message = data == null ? null : data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
82
+ if (message == null) {
83
+ callback.error(CustomExceptions.RETRIEVE_FAILED);
84
+ } else {
85
+ callback.success(new RetrieveSmsResult(message));
86
+ }
87
+ }
88
+
89
+ public boolean isGooglePlayServicesAvailable() {
90
+ int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(plugin.getContext());
91
+ return result == ConnectionResult.SUCCESS;
92
+ }
93
+
94
+ public void requestPhoneNumber(@NonNull NonEmptyResultCallback<RequestPhoneNumberResult> callback) {
95
+ if (pendingPhoneNumberCallback != null) {
96
+ callback.error(CustomExceptions.OPERATION_IN_PROGRESS);
97
+ return;
98
+ }
99
+ GetPhoneNumberHintIntentRequest request = GetPhoneNumberHintIntentRequest.builder().build();
100
+ Identity.getSignInClient(plugin.getActivity())
101
+ .getPhoneNumberHintIntent(request)
102
+ .addOnSuccessListener(pendingIntent -> {
103
+ pendingPhoneNumberCallback = callback;
104
+ plugin.launchPhoneNumberHintIntent(pendingIntent);
105
+ })
106
+ .addOnFailureListener(exception -> callback.error(CustomExceptions.PHONE_NUMBER_HINT_FAILED));
107
+ }
108
+
109
+ public void retrieveSms(@NonNull RetrieveSmsOptions options, @NonNull NonEmptyResultCallback<RetrieveSmsResult> callback) {
110
+ if (pendingRetrieveSmsCallback != null) {
111
+ callback.error(CustomExceptions.OPERATION_IN_PROGRESS);
112
+ return;
113
+ }
114
+ pendingRetrieveSmsCallback = callback;
115
+ registerSmsBroadcastReceiver();
116
+ SmsRetriever.getClient(plugin.getContext())
117
+ .startSmsUserConsent(options.getSenderPhoneNumber())
118
+ .addOnFailureListener(exception -> rejectPendingRetrieveSms(CustomExceptions.RETRIEVE_FAILED));
119
+ }
120
+
121
+ private void handleSmsBroadcast(@NonNull Intent intent) {
122
+ if (!SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
123
+ return;
124
+ }
125
+ Bundle extras = intent.getExtras();
126
+ if (extras == null) {
127
+ rejectPendingRetrieveSms(CustomExceptions.RETRIEVE_FAILED);
128
+ return;
129
+ }
130
+ Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
131
+ if (status == null) {
132
+ rejectPendingRetrieveSms(CustomExceptions.RETRIEVE_FAILED);
133
+ return;
134
+ }
135
+ switch (status.getStatusCode()) {
136
+ case CommonStatusCodes.SUCCESS:
137
+ Intent consentIntent = (Intent) extras.get(SmsRetriever.EXTRA_CONSENT_INTENT);
138
+ unregisterSmsBroadcastReceiver();
139
+ if (consentIntent == null) {
140
+ rejectPendingRetrieveSms(CustomExceptions.RETRIEVE_FAILED);
141
+ } else {
142
+ plugin.launchSmsConsentIntent(consentIntent);
143
+ }
144
+ break;
145
+ case CommonStatusCodes.TIMEOUT:
146
+ rejectPendingRetrieveSms(CustomExceptions.TIMEOUT);
147
+ break;
148
+ default:
149
+ rejectPendingRetrieveSms(CustomExceptions.RETRIEVE_FAILED);
150
+ break;
151
+ }
152
+ }
153
+
154
+ private void registerSmsBroadcastReceiver() {
155
+ unregisterSmsBroadcastReceiver();
156
+ smsBroadcastReceiver = new BroadcastReceiver() {
157
+ @Override
158
+ public void onReceive(Context context, Intent intent) {
159
+ handleSmsBroadcast(intent);
160
+ }
161
+ };
162
+ IntentFilter filter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION);
163
+ ContextCompat.registerReceiver(
164
+ plugin.getContext(),
165
+ smsBroadcastReceiver,
166
+ filter,
167
+ SmsRetriever.SEND_PERMISSION,
168
+ null,
169
+ ContextCompat.RECEIVER_EXPORTED
170
+ );
171
+ }
172
+
173
+ private void rejectPendingRetrieveSms(@NonNull Exception exception) {
174
+ unregisterSmsBroadcastReceiver();
175
+ if (pendingRetrieveSmsCallback == null) {
176
+ return;
177
+ }
178
+ NonEmptyResultCallback<RetrieveSmsResult> callback = pendingRetrieveSmsCallback;
179
+ pendingRetrieveSmsCallback = null;
180
+ callback.error(exception);
181
+ }
182
+
183
+ private void unregisterSmsBroadcastReceiver() {
184
+ if (smsBroadcastReceiver == null) {
185
+ return;
186
+ }
187
+ try {
188
+ plugin.getContext().unregisterReceiver(smsBroadcastReceiver);
189
+ } catch (IllegalArgumentException exception) {
190
+ // The receiver was not registered.
191
+ }
192
+ smsBroadcastReceiver = null;
193
+ }
194
+ }
@@ -0,0 +1,153 @@
1
+ package io.capawesome.capacitorjs.plugins.androidsmsretriever;
2
+
3
+ import android.app.Activity;
4
+ import android.app.PendingIntent;
5
+ import android.content.Intent;
6
+ import androidx.activity.result.ActivityResult;
7
+ import androidx.activity.result.ActivityResultLauncher;
8
+ import androidx.activity.result.IntentSenderRequest;
9
+ import androidx.activity.result.contract.ActivityResultContracts;
10
+ import androidx.annotation.NonNull;
11
+ import androidx.annotation.Nullable;
12
+ import com.getcapacitor.Logger;
13
+ import com.getcapacitor.Plugin;
14
+ import com.getcapacitor.PluginCall;
15
+ import com.getcapacitor.PluginMethod;
16
+ import com.getcapacitor.annotation.CapacitorPlugin;
17
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.CustomException;
18
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.options.RetrieveSmsOptions;
19
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.results.RequestPhoneNumberResult;
20
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.classes.results.RetrieveSmsResult;
21
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.interfaces.NonEmptyResultCallback;
22
+ import io.capawesome.capacitorjs.plugins.androidsmsretriever.interfaces.Result;
23
+
24
+ @CapacitorPlugin(name = "AndroidSmsRetriever")
25
+ public class AndroidSmsRetrieverPlugin extends Plugin {
26
+
27
+ public static final String ERROR_UNKNOWN_ERROR = "An unknown error occurred.";
28
+ public static final String TAG = "AndroidSmsRetrieverPlugin";
29
+
30
+ private AndroidSmsRetriever implementation;
31
+ private ActivityResultLauncher<IntentSenderRequest> phoneNumberHintLauncher;
32
+ private ActivityResultLauncher<Intent> smsConsentLauncher;
33
+
34
+ @Override
35
+ public void load() {
36
+ implementation = new AndroidSmsRetriever(this);
37
+ phoneNumberHintLauncher = getActivity()
38
+ .registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), this::handlePhoneNumberHintResult);
39
+ smsConsentLauncher = getActivity()
40
+ .registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this::handleSmsConsentResult);
41
+ }
42
+
43
+ @Override
44
+ protected void handleOnDestroy() {
45
+ super.handleOnDestroy();
46
+ if (implementation != null) {
47
+ implementation.handleOnDestroy();
48
+ }
49
+ }
50
+
51
+ public void launchPhoneNumberHintIntent(@NonNull PendingIntent pendingIntent) {
52
+ IntentSenderRequest request = new IntentSenderRequest.Builder(pendingIntent).build();
53
+ phoneNumberHintLauncher.launch(request);
54
+ }
55
+
56
+ public void launchSmsConsentIntent(@NonNull Intent intent) {
57
+ smsConsentLauncher.launch(intent);
58
+ }
59
+
60
+ @PluginMethod
61
+ public void requestPhoneNumber(PluginCall call) {
62
+ try {
63
+ if (!implementation.isGooglePlayServicesAvailable()) {
64
+ rejectCallAsUnavailable(call);
65
+ return;
66
+ }
67
+
68
+ NonEmptyResultCallback<RequestPhoneNumberResult> callback = new NonEmptyResultCallback<>() {
69
+ @Override
70
+ public void success(@NonNull RequestPhoneNumberResult result) {
71
+ resolveCall(call, result);
72
+ }
73
+
74
+ @Override
75
+ public void error(@NonNull Exception exception) {
76
+ rejectCall(call, exception);
77
+ }
78
+ };
79
+
80
+ implementation.requestPhoneNumber(callback);
81
+ } catch (Exception exception) {
82
+ rejectCall(call, exception);
83
+ }
84
+ }
85
+
86
+ @PluginMethod
87
+ public void retrieveSms(PluginCall call) {
88
+ try {
89
+ if (!implementation.isGooglePlayServicesAvailable()) {
90
+ rejectCallAsUnavailable(call);
91
+ return;
92
+ }
93
+
94
+ RetrieveSmsOptions options = new RetrieveSmsOptions(call);
95
+ NonEmptyResultCallback<RetrieveSmsResult> callback = new NonEmptyResultCallback<>() {
96
+ @Override
97
+ public void success(@NonNull RetrieveSmsResult result) {
98
+ resolveCall(call, result);
99
+ }
100
+
101
+ @Override
102
+ public void error(@NonNull Exception exception) {
103
+ rejectCall(call, exception);
104
+ }
105
+ };
106
+
107
+ implementation.retrieveSms(options, callback);
108
+ } catch (Exception exception) {
109
+ rejectCall(call, exception);
110
+ }
111
+ }
112
+
113
+ private void handlePhoneNumberHintResult(@NonNull ActivityResult result) {
114
+ if (result.getResultCode() == Activity.RESULT_OK) {
115
+ implementation.handlePhoneNumberResult(result.getData());
116
+ } else {
117
+ implementation.handlePhoneNumberCanceled();
118
+ }
119
+ }
120
+
121
+ private void handleSmsConsentResult(@NonNull ActivityResult result) {
122
+ if (result.getResultCode() == Activity.RESULT_OK) {
123
+ implementation.handleSmsConsentResult(result.getData());
124
+ } else {
125
+ implementation.handleSmsConsentCanceled();
126
+ }
127
+ }
128
+
129
+ private void rejectCall(@NonNull PluginCall call, @NonNull Exception exception) {
130
+ String message = exception.getMessage();
131
+ if (message == null) {
132
+ message = ERROR_UNKNOWN_ERROR;
133
+ }
134
+ String code = null;
135
+ if (exception instanceof CustomException) {
136
+ code = ((CustomException) exception).getCode();
137
+ }
138
+ Logger.error(TAG, message, exception);
139
+ call.reject(message, code);
140
+ }
141
+
142
+ private void rejectCallAsUnavailable(@NonNull PluginCall call) {
143
+ call.unavailable("Google Play services is not available on this device.");
144
+ }
145
+
146
+ private void resolveCall(@NonNull PluginCall call, @Nullable Result result) {
147
+ if (result == null) {
148
+ call.resolve();
149
+ } else {
150
+ call.resolve(result.toJSObject());
151
+ }
152
+ }
153
+ }