@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.
- package/CapawesomeCapacitorAndroidSmsRetriever.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +206 -0
- package/android/build.gradle +62 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/AndroidSmsRetriever.java +194 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/AndroidSmsRetrieverPlugin.java +153 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/CustomExceptions.java +17 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/options/RetrieveSmsOptions.java +25 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/results/RequestPhoneNumberResult.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/classes/results/RetrieveSmsResult.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/NonEmptyResultCallback.java +7 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/androidsmsretriever/interfaces/Result.java +7 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +147 -0
- package/dist/esm/definitions.d.ts +105 -0
- package/dist/esm/definitions.js +37 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +61 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +64 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/AndroidSmsRetrieverPlugin.swift +24 -0
- package/ios/Plugin/Info.plist +24 -0
- 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<<a href="#requestphonenumberresult">RequestPhoneNumberResult</a>></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<<a href="#retrievesmsresult">RetrieveSmsResult</a>></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
|
+
}
|