Socket
Book a DemoInstallSign in
Socket

@avasapp/react-native-otp-autofill

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@avasapp/react-native-otp-autofill

Automatic SMS Verification with the SMS Retriever API

1.0.2
latest
Source
npmnpm
Version published
Weekly downloads
9
-66.67%
Maintainers
1
Weekly downloads
 
Created
Source

React Native OTP Autofill

An Expo module for automatic SMS verification using Android SMS Retriever API.

Requirements

  • Android API 19+
  • Google Play Services
  • Expo SDK 49+

Features

  • ✅ Automatic SMS verification using Android SMS Retriever API
  • ✅ App signature hash generation for SMS verification
  • ✅ OTP extraction from SMS messages
  • ✅ Event-based listener system
  • ✅ TypeScript support
  • ✅ Expo modules API

Installation

This module is available as an npm package. To use it in your Expo/React Native app:

npm

npm install @avasapp/react-native-otp-autofill

bun

bun add @avasapp/react-native-otp-autofill

yarn

yarn add @avasapp/react-native-otp-autofill

Quick Start

After installation, you can use the React hooks for the simplest integration:

import { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'

// In your component
const { hash } = useGetHash()
const { startListener, receivedOtp } = useOtpListener()

Usage

The module provides React hooks for easy integration with modern React apps:

useGetHash Hook

import React from 'react'
import { useGetHash } from '@avasapp/react-native-otp-autofill'

const AppHashComponent = () => {
  const { hash, loading, error, refetch } = useGetHash({
    onSuccess: (hash) => {
      console.log('App hash loaded:', hash)
    },
    onError: (error) => {
      console.error('Failed to get app hash:', error)
    },
  })

  if (loading) return <p>Loading app hash...</p>
  if (error) return <p>Error: {error.message}</p>

  return (
    <div>
      <p>App Hash: {hash}</p>
      <button onClick={refetch}>Refresh Hash</button>
    </div>
  )
}

useOtpListener Hook

import React from 'react'
import { useOtpListener } from '@avasapp/react-native-otp-autofill'

const SmsVerificationComponent = () => {
  const {
    isListening,
    loading,
    receivedOtp,
    receivedMessage,
    error,
    startListener,
    stopListener,
  } = useOtpListener({
    onOtpReceived: (otp, message) => {
      console.log('OTP received:', otp)
      console.log('Full message:', message)
      // Process the OTP
    },
    onTimeout: (message) => {
      console.log('SMS timeout:', message)
    },
    onError: (error, code) => {
      console.error('SMS error:', error, 'Code:', code)
    },
  })

  return (
    <div>
      <button onClick={startListener} disabled={isListening || loading}>
        {loading
          ? 'Starting...'
          : isListening
          ? 'Listening...'
          : 'Start SMS Listener'}
      </button>

      <button onClick={stopListener} disabled={!isListening}>
        Stop Listener
      </button>

      {receivedOtp && <p>OTP: {receivedOtp}</p>}
      {error && <p>Error: {error}</p>}
    </div>
  )
}

Complete Example with Both Hooks

import React, { useState } from 'react'
import { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'

const SmsVerificationFlow = () => {
  const [step, setStep] = useState<'hash' | 'sms' | 'complete'>('hash')
  const [phoneNumber, setPhoneNumber] = useState('')

  // Get app hash first
  const {
    hash,
    loading: hashLoading,
    error: hashError,
  } = useGetHash({
    onSuccess: (hash) => {
      console.log('Ready to send SMS with hash:', hash)
      setStep('sms')
    },
  })

  // Listen for SMS
  const { isListening, receivedOtp, startListener, stopListener } =
    useOtpListener({
      onOtpReceived: (otp) => {
        console.log('Verification complete:', otp)
        setStep('complete')
        stopListener()
      },
    })

  const sendSms = async () => {
    if (!hash) return

    // Send SMS with your backend API
    await fetch('/api/send-sms', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        phoneNumber,
        appHash: hash,
      }),
    })

    // Start listening for SMS
    startListener()
  }

  if (step === 'hash') {
    return (
      <div>
        <p>Preparing SMS verification...</p>
        {hashLoading && <p>Loading...</p>}
        {hashError && <p>Error: {hashError.message}</p>}
        {hash && <p>Ready! Hash: {hash}</p>}
      </div>
    )
  }

  if (step === 'sms') {
    return (
      <div>
        <input
          type="tel"
          placeholder="Phone number"
          value={phoneNumber}
          onChange={(e) => setPhoneNumber(e.target.value)}
        />
        <button onClick={sendSms} disabled={!phoneNumber}>
          Send SMS
        </button>
        {isListening && <p>Waiting for SMS...</p>}
      </div>
    )
  }

  return (
    <div>
      <p>✅ Verification complete!</p>
      <p>OTP: {receivedOtp}</p>
    </div>
  )
}

Manual API (no hooks)

If you prefer direct control without hooks, use the module methods and event listeners:

import { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'

// Get app signature hash (use the first one)
const getAppHash = async (): Promise<string | undefined> => {
  try {
    const hashes = await AvasOtpAutofillModule.getHash()
    console.log('App signature hashes:', hashes)
    return hashes[0]
  } catch (error) {
    console.error('Error getting app hash:', error)
  }
}

// Start listening for SMS and wire up events
const startSmsListener = async () => {
  const subs = [] as import('expo-modules-core').EventSubscription[]

  subs.push(
    AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) => {
      console.log('OTP received:', otp)
      console.log('Full message:', message)
      // ...verify with your backend
    }),
  )

  subs.push(
    AvasOtpAutofillModule.addListener('onTimeout', ({ message }) => {
      console.log('SMS timeout:', message)
    }),
  )

  subs.push(
    AvasOtpAutofillModule.addListener('onError', ({ message, code }) => {
      console.error('SMS error:', message, 'code:', code)
    }),
  )

  await AvasOtpAutofillModule.startOtpListener()

  // Return a cleanup function
  return () => {
    subs.forEach((s) => s.remove())
    AvasOtpAutofillModule.stopSmsRetriever()
  }
}

Manual example (React component)

import React, { useRef, useState } from 'react'
import { Button, Text, View } from 'react-native'
import { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'

export const ManualSmsVerification = () => {
  const [otp, setOtp] = useState<string | null>(null)
  const [message, setMessage] = useState<string | null>(null)
  const [isListening, setIsListening] = useState(false)
  const cleanupRef = useRef<null | (() => void)>(null)

  const start = async () => {
    if (isListening) return
    setIsListening(true)
    setOtp(null)
    setMessage(null)

    // Register listeners first
    const subs = [
      AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) => {
        setOtp(otp ?? null)
        setMessage(message)
        setIsListening(false)
        cleanup()
      }),
      AvasOtpAutofillModule.addListener('onTimeout', () => {
        setIsListening(false)
        cleanup()
      }),
      AvasOtpAutofillModule.addListener('onError', () => {
        setIsListening(false)
        cleanup()
      }),
    ]

    const cleanup = () => {
      subs.forEach((s) => s.remove())
      AvasOtpAutofillModule.stopSmsRetriever()
      cleanupRef.current = null
    }

    cleanupRef.current = cleanup
    await AvasOtpAutofillModule.startOtpListener()
  }

  const stop = () => {
    cleanupRef.current?.()
    setIsListening(false)
  }

  return (
    <View>
      <Button title={isListening ? 'Listening…' : 'Start SMS Listener'} onPress={start} disabled={isListening} />
      {isListening && <Button title="Stop" onPress={stop} />}
      {otp && <Text>OTP: {otp}</Text>}
      {message && <Text>Message: {message}</Text>}
    </View>
  )
}

API Reference

React Hooks

useGetHash(options?: UseGetHashOptions): UseGetHashReturn

A React hook for managing app signature hash retrieval with automatic loading states and error handling.

Options:

interface UseGetHashOptions {
  onSuccess?: (value: string) => void
  onError?: (error: Error) => void
}

Returns:

interface UseGetHashReturn {
  hash: string | null // The app signature hash
  loading: boolean // Whether hash is being fetched
  error: Error | null // Any error that occurred
  refetch: () => Promise<void> // Function to refetch the hash
}

Example:

const { hash, loading, error, refetch } = useGetHash({
  onSuccess: (hash) => console.log('Hash:', hash),
  onError: (error) => console.error('Error:', error),
})

useOtpListener(options?: UseOtpListenerOptions): UseOtpListenerReturn

A React hook for managing SMS OTP listening with automatic cleanup and state management.

Options:

interface UseOtpListenerOptions {
  onOtpReceived?: (otp: string, message: string) => void
  onTimeout?: (message: string) => void
  onError?: (error: string, code: number) => void
}

Returns:

interface UseOtpListenerReturn {
  isListening: boolean // Whether actively listening for SMS
  loading: boolean // Whether starting/stopping listener
  receivedOtp: string | null // Last received OTP
  receivedMessage: string | null // Full SMS message
  error: string | null // Any error message
  startListener: () => Promise<void> // Start listening for SMS
  stopListener: () => void // Stop listening and cleanup
}

Example:

const { isListening, receivedOtp, startListener, stopListener } =
  useOtpListener({
    onOtpReceived: (otp, message) => {
      console.log('OTP:', otp, 'Message:', message)
    },
  })

Manual Methods

getOtp(): Promise<boolean>

Starts the SMS retriever and returns whether it was successfully started.

const success = await AvasOtpAutofill.getOtp()

getHash(): Promise<string[]>

Returns the app signature hashes needed for SMS verification.

const hashes = await AvasOtpAutofill.getHash()

requestHint(): Promise<string>

Requests a phone number hint (placeholder implementation).

const hint = await AvasOtpAutofill.requestHint()

startOtpListener(): Promise<boolean>

Starts listening for SMS using the Android SMS Retriever API.

await AvasOtpAutofillModule.startOtpListener()

addListener(eventName, listener): EventSubscription

Adds a listener for module events. Call before startOtpListener().

const sub = AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) => {
  console.log('OTP:', otp, 'Message:', message)
})

Cleanup

Use the returned subscription to remove listeners, and stop the retriever when done.

sub.remove()
await AvasOtpAutofillModule.stopSmsRetriever()

Events

The module emits the following events:

  • onSmsReceived: When an SMS is received with OTP
  • onTimeout: When SMS retriever times out (after 5 minutes)
  • onError: When an error occurs

Event payloads:

type SmsReceivedEventPayload = {
  message: string
  otp: string | null
}

type TimeoutEventPayload = {
  message: string
}

type ErrorEventPayload = {
  message: string
  code: number
}

SMS Format Requirements

For the SMS Retriever API to work, the SMS message must:

  • Contain a verification code (4-6 digits)
  • Include your app's signature hash
  • Be no longer than 140 bytes
  • Contain a one-time code that the user has never seen before

Example SMS Format

Your verification code is: 123456

FA+9qCX9VSu

Where FA+9qCX9VSu is your app's signature hash.

Troubleshooting

Common Issues

  • SMS not received: Ensure your SMS includes the correct app signature hash
  • Module not found: Make sure the module is properly installed and linked
  • Timeout errors: SMS Retriever has a 5-minute timeout limit
  • Hooks not updating: Make sure you're using the hooks inside React components
  • Multiple listeners: Use stopListener() before starting a new listener

Debug Mode

Using Hooks for Debugging

import { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'

const DebugComponent = () => {
  const { hash, loading, error } = useGetHash({
    onSuccess: (hash) => console.log('✅ Hash loaded:', hash),
    onError: (error) => console.error('❌ Hash error:', error),
  })

  const {
    isListening,
    receivedOtp,
    receivedMessage,
    error: smsError,
  } = useOtpListener({
    onOtpReceived: (otp, message) => {
      console.log('📱 SMS received:', { otp, message })
    },
    onTimeout: (message) => {
      console.log('⏰ SMS timeout:', message)
    },
    onError: (error, code) => {
      console.error('❌ SMS error:', { error, code })
    },
  })

  return (
    <div>
      <p>Hash: {hash || 'Loading...'}</p>
      <p>Listening: {isListening ? 'Yes' : 'No'}</p>
      <p>OTP: {receivedOtp || 'None'}</p>
      {error && <p>Hash Error: {error.message}</p>}
      {smsError && <p>SMS Error: {smsError}</p>}
    </div>
  )
}

Manual Event Debugging

import { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'

// Listen to all events for debugging
AvasOtpAutofillModule.addListener('onSmsReceived', (event) => {
  console.log('📱 SMS received:', event)
})

AvasOtpAutofillModule.addListener('onTimeout', (event) => {
  console.log('⏰ SMS timeout:', event)
})

AvasOtpAutofillModule.addListener('onError', (event) => {
  console.log('❌ SMS error:', event)
})

Performance Tips

  • Use hooks at component level: Don't call hooks conditionally or in loops
  • Clean up listeners: Always call stopListener() when component unmounts
  • Avoid multiple hash fetches: Use refetch() from useGetHash instead of creating new instances
  • Handle loading states: Show loading indicators to improve user experience

Development

Building the Module

# Install dependencies
bun install

# Build the module
bun run build

# Clean build artifacts
bun run clean

# Run linting
bun run lint

# Run tests
bun run test

Publishing

This package is published to the npm registry. To publish a new version:

  • Update the version in package.json
  • Build and publish:
    bun run build
    npm publish --access public
    

Local Development

For local development and testing:

# Link the package locally
bun link

# In your test project
bun link @avasapp/react-native-otp-autofill

License

MIT

Keywords

react-native

FAQs

Package last updated on 11 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.