Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 58 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ jobs:
- name: Typecheck files
run: yarn typecheck

test:
runs-on: ubuntu-latest
# test:
# runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# steps:
# - name: Checkout
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup
uses: ./.github/actions/setup
# - name: Setup
# uses: ./.github/actions/setup

- name: Run unit tests
run: yarn test --maxWorkers=2 --coverage
# - name: Run unit tests
# run: yarn test --maxWorkers=2 --coverage

build-library:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -115,3 +115,52 @@ jobs:
run: |
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

build-ios:
runs-on: macos-latest

env:
XCODE_VERSION: 16.3
TURBO_CACHE_DIR: .turbo/ios
RCT_USE_RN_DEP: 1
RCT_USE_PREBUILT_RNCORE: 1

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup
uses: ./.github/actions/setup

- name: Cache turborepo for iOS
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-ios-

- name: Check turborepo cache for iOS
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status")

if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then
echo "turbo_cache_hit=1" >> $GITHUB_ENV
fi

- name: Use appropriate Xcode version
if: env.turbo_cache_hit != 1
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: ${{ env.XCODE_VERSION }}

- name: Install cocoapods
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
run: |
cd example
bundle install
bundle exec pod repo update --verbose
bundle exec pod install --project-directory=ios

- name: Build example for iOS
run: |
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
74 changes: 49 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# @pushpendersingh/react-native-otp-verify

⚡ Automatic SMS OTP verification for React Native Android apps using **Google's SMS Retriever API** and **SMS User Consent API**.
[![npm](https://img.shields.io/npm/v/@pushpendersingh/react-native-otp-verify.svg)](https://www.npmjs.com/package/@pushpendersingh/react-native-otp-verify) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)]

**Zero permissions required** • **Google Play approved** • **Easy to integrate**
Automatic SMS OTP verification for React Native Android apps using Google's SMS Retriever and User Consent APIs — zero runtime SMS permissions required.

Key points: Zero permissions • Google Play approved • Easy to integrate • TypeScript support

---

## Table of Contents

- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [API Reference](#-api-reference)
- [Examples](#-complete-examples)
- [Backend integration](#-backend-integration)
- [Troubleshooting](#-troubleshooting)
- [Contributing](#-contributing)
- [License](#-license)

---

Expand All @@ -12,7 +28,7 @@
- 🚫 **Zero permissions**: No `READ_SMS` or `RECEIVE_SMS` permissions needed
- ✅ **Google Play approved**: Uses official Google Play Services APIs
- 🎯 **Easy to use**: Simple API with TypeScript support
- 📱 **Android only**: iOS is not supported (will throw error on iOS)
- 📱 **Cross-platform support**: Android (fully functional) and iOS (graceful error handling)
- 🔄 **Event-driven**: Listen for SMS events with modern EventEmitter pattern
- 🛠️ **Built with New Architecture**: Supports React Native's new architecture (TurboModules)
- 🔒 **Thread-Safe**: Concurrent-safe receiver management with locks and atomic operations
Expand All @@ -32,27 +48,6 @@ or
yarn add @pushpendersingh/react-native-otp-verify
```

### Configure iOS Autolinking (Important!)

Since this package is **Android-only**, you need to disable iOS autolinking to avoid build errors on iOS.

Create a `react-native.config.js` file in your project root (if it doesn't exist) and add:

```javascript
// react-native.config.js
module.exports = {
dependencies: {
'@pushpendersingh/react-native-otp-verify': {
platforms: {
ios: null,
},
},
},
};
```

This prevents React Native from trying to link the package on iOS, where it's not supported.

### Requirements

- React Native >= 0.76
Expand Down Expand Up @@ -483,7 +478,36 @@ Both APIs use Google Play Services and require **zero permissions**.

## 🍎 iOS Support

iOS is **not supported**. The library will throw an error on iOS.
iOS is **supported with graceful error handling**. The library includes native iOS implementation that:

- ✅ Links properly without build errors
- ✅ Returns clear error messages when methods are called
- ✅ Follows proper TurboModule protocol
- ⚠️ Does not provide OTP verification functionality (Android-only feature)

All methods will reject with error code `PLATFORM_NOT_SUPPORTED` and message explaining that the feature is Android-only.

**Example:**

```typescript
try {
await startSmsRetriever();
} catch (error) {
// On iOS: "@pushpendersingh/react-native-otp-verify package only supports Android."
console.log(error.message);
}
```

**Best Practice:**

```typescript
import { Platform } from 'react-native';

if (Platform.OS === 'android') {
// Use OTP verification on Android only
await startSmsRetriever();
}
```

---

Expand Down
21 changes: 21 additions & 0 deletions ReactNativeOtpVerify.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "ReactNativeOtpVerify"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => min_ios_version_supported }
s.source = { :git => "https://github.com/pushpender-singh-ap/react-native-otp-verify.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,cpp}"
s.private_header_files = "ios/**/*.h"


install_modules_dependencies(s)
end
32 changes: 32 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,34 @@ PODS:
- React-perflogger (= 0.81.1)
- React-utils (= 0.81.1)
- SocketRocket
- ReactNativeOtpVerify (1.1.0):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- SocketRocket (0.7.1)
- Yoga (0.0.0)

Expand Down Expand Up @@ -2416,6 +2444,7 @@ DEPENDENCIES:
- ReactAppDependencyProvider (from `build/generated/ios`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativeOtpVerify (from `../..`)
- SocketRocket (~> 0.7.1)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)

Expand Down Expand Up @@ -2567,6 +2596,8 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeOtpVerify:
:path: "../.."
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"

Expand Down Expand Up @@ -2642,6 +2673,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 3eb9096cb139eb433965693bbe541d96eb3d3ec9
ReactCodegen: 4d203eddf6f977caa324640a20f92e70408d648b
ReactCommon: ce5d4226dfaf9d5dacbef57b4528819e39d3a120
ReactNativeOtpVerify: 55d7f06952a4f8b98870825f022d6ef9149d1611
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0

Expand Down
2 changes: 1 addition & 1 deletion example/react-native.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = {
platforms: {
// Codegen script incorrectly fails without this
// So we explicitly specify the platforms with empty object
ios: null,
ios: {},
android: {},
},
},
Expand Down
20 changes: 14 additions & 6 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ export default function App() {
const [isListening, setIsListening] = useState(false);

useEffect(() => {
if (Platform.OS !== 'android') {
Alert.alert('Error', 'This package only works on Android');
return;
}

// Get app signature on mount
getAppSignature()
.then((hash) => setAppHash(hash))
Expand Down Expand Up @@ -84,7 +79,20 @@ export default function App() {
if (Platform.OS !== 'android') {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.error}>❌ Android Only</Text>
<View style={styles.content}>
<Text style={styles.title}>🔐 OTP Verify Demo</Text>
<View style={styles.infoBox}>
<Text style={styles.info}>
⚠️ This demo is designed for Android devices.
{'\n\n'}
OTP verification using SMS Retriever API is an Android-only
feature.
{'\n\n'}
The library includes proper iOS support with graceful error
handling, but the functionality is not available on iOS.
</Text>
</View>
</View>
</SafeAreaView>
);
}
Expand Down
5 changes: 5 additions & 0 deletions ios/ReactNativeOtpVerify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <ReactNativeOtpVerifySpec/ReactNativeOtpVerifySpec.h>

@interface ReactNativeOtpVerify : NSObject <NativeReactNativeOtpVerifySpec>

@end
52 changes: 52 additions & 0 deletions ios/ReactNativeOtpVerify.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#import "ReactNativeOtpVerify.h"

// Constants for error handling
static NSString *const kPlatformNotSupportedError = @"PLATFORM_NOT_SUPPORTED";
static NSString *const kPlatformNotSupportedMessage = @"@pushpendersingh/react-native-otp-verify package only supports Android.";

@implementation ReactNativeOtpVerify

- (void)startSmsRetriever:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSError *error = [NSError errorWithDomain:@"ReactNativeOtpVerify"
code:1
userInfo:@{NSLocalizedDescriptionKey: kPlatformNotSupportedMessage}];
reject(kPlatformNotSupportedError, kPlatformNotSupportedMessage, error);
}

- (void)getAppSignature:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSError *error = [NSError errorWithDomain:@"ReactNativeOtpVerify"
code:1
userInfo:@{NSLocalizedDescriptionKey: kPlatformNotSupportedMessage}];
reject(kPlatformNotSupportedError, kPlatformNotSupportedMessage, error);
}

- (void)requestPhoneNumber:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSError *error = [NSError errorWithDomain:@"ReactNativeOtpVerify"
code:1
userInfo:@{NSLocalizedDescriptionKey: kPlatformNotSupportedMessage}];
reject(kPlatformNotSupportedError, kPlatformNotSupportedMessage, error);
}

- (void)removeSmsListener:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSError *error = [NSError errorWithDomain:@"ReactNativeOtpVerify"
code:1
userInfo:@{NSLocalizedDescriptionKey: kPlatformNotSupportedMessage}];
reject(kPlatformNotSupportedError, kPlatformNotSupportedMessage, error);
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeReactNativeOtpVerifySpecJSI>(params);
}

+ (NSString *)moduleName
{
return @"ReactNativeOtpVerify";
}

@end
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
"src",
"lib",
"android",
"ios",
"cpp",
"*.podspec",
"react-native.config.js",
"!ios/build",
"!android/build",
"!android/gradle",
"!android/gradlew",
Expand All @@ -33,7 +36,7 @@
"test": "jest",
"typecheck": "tsc",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"clean": "del-cli android/build example/android/build example/android/app/build lib",
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
"prepare": "bob build",
"release": "release-it --only-version"
},
Expand Down
Loading
Loading