{"id":31822784,"url":"https://github.com/avas-app/react-native-android-otp-autofill","last_synced_at":"2025-10-11T14:17:19.270Z","repository":{"id":307268615,"uuid":"1027553450","full_name":"avas-app/react-native-android-otp-autofill","owner":"avas-app","description":"Automatic SMS Verification / AutoFill for Android with the SMS Retriever API. Implemented with Expo Modules for React Native Applications.","archived":false,"fork":false,"pushed_at":"2025-10-08T08:08:12.000Z","size":302,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-08T09:26:55.620Z","etag":null,"topics":["android","autofill","expo","expo-modules","otp-verification","react-native"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avas-app.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-28T07:24:39.000Z","updated_at":"2025-10-08T08:07:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"045d19ba-e78d-4195-bfb2-165dc59ddf72","html_url":"https://github.com/avas-app/react-native-android-otp-autofill","commit_stats":null,"previous_names":["avas-app/react-native-android-otp-autofill"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/avas-app/react-native-android-otp-autofill","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avas-app%2Freact-native-android-otp-autofill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avas-app%2Freact-native-android-otp-autofill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avas-app%2Freact-native-android-otp-autofill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avas-app%2Freact-native-android-otp-autofill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avas-app","download_url":"https://codeload.github.com/avas-app/react-native-android-otp-autofill/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avas-app%2Freact-native-android-otp-autofill/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002693,"owners_count":26083442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","autofill","expo","expo-modules","otp-verification","react-native"],"created_at":"2025-10-11T14:17:16.154Z","updated_at":"2025-10-11T14:17:19.259Z","avatar_url":"https://github.com/avas-app.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Native OTP Autofill\n\nAn Expo module for automatic SMS verification using Android SMS Retriever API.\n\n[![NPM Version](https://img.shields.io/npm/v/%40avasapp%2Freact-native-otp-autofill?style=for-the-badge\u0026color=%23EA3F00)](https://www.npmjs.com/package/@avasapp/react-native-otp-autofill)\n[![npm bundle size](https://img.shields.io/bundlephobia/min/%40avasapp%2Freact-native-otp-autofill?style=for-the-badge\u0026color=%23FFFFFF)](https://bundlephobia.com/package/@avasapp/react-native-otp-autofill)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40avasapp%2Freact-native-otp-autofill?style=for-the-badge\u0026color=%23FFFFFF)](https://bundlephobia.com/package/@avasapp/react-native-otp-autofill)\n![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/avas-app/react-native-android-otp-autofill/publish.yml?style=for-the-badge)\n\n## Requirements\n\n- Android API 19+\n- Google Play Services\n- Expo SDK 49+\n\n## Features\n\n- ✅ Automatic SMS verification using Android SMS Retriever API\n- ✅ App signature hash generation for SMS verification\n- ✅ OTP extraction from SMS messages\n- ✅ Event-based listener system\n- ✅ TypeScript support\n- ✅ Expo modules API\n\n## Installation\n\nThis module is available as an npm package. To use it in your Expo/React Native app:\n\n### npm\n\n```bash\nnpm install @avasapp/react-native-otp-autofill\n```\n\n### bun\n\n```bash\nbun add @avasapp/react-native-otp-autofill\n```\n\n### yarn\n\n```bash\nyarn add @avasapp/react-native-otp-autofill\n```\n\n### Quick Start\n\nAfter installation, you can use the React hooks for the simplest integration:\n\n```typescript\nimport { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'\n\n// In your component\nconst { hash } = useGetHash()\nconst { startListener, receivedOtp } = useOtpListener()\n```\n\n## Usage\n\n### React Hooks API (Recommended)\n\nThe module provides React hooks for easy integration with modern React apps:\n\n#### useGetHash Hook\n\n```typescript\nimport React from 'react'\nimport { useGetHash } from '@avasapp/react-native-otp-autofill'\n\nconst AppHashComponent = () =\u003e {\n  const { hash, loading, error, refetch } = useGetHash({\n    onSuccess: (hash) =\u003e {\n      console.log('App hash loaded:', hash)\n    },\n    onError: (error) =\u003e {\n      console.error('Failed to get app hash:', error)\n    },\n  })\n\n  if (loading) return \u003cp\u003eLoading app hash...\u003c/p\u003e\n  if (error) return \u003cp\u003eError: {error.message}\u003c/p\u003e\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eApp Hash: {hash}\u003c/p\u003e\n      \u003cbutton onClick={refetch}\u003eRefresh Hash\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n#### useOtpListener Hook\n\n```typescript\nimport React from 'react'\nimport { useOtpListener } from '@avasapp/react-native-otp-autofill'\n\nconst SmsVerificationComponent = () =\u003e {\n  const {\n    isListening,\n    loading,\n    receivedOtp,\n    receivedMessage,\n    error,\n    startListener,\n    stopListener,\n  } = useOtpListener({\n    onOtpReceived: (otp, message) =\u003e {\n      console.log('OTP received:', otp)\n      console.log('Full message:', message)\n      // Process the OTP\n    },\n    onTimeout: (message) =\u003e {\n      console.log('SMS timeout:', message)\n    },\n    onError: (error, code) =\u003e {\n      console.error('SMS error:', error, 'Code:', code)\n    },\n  })\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={startListener} disabled={isListening || loading}\u003e\n        {loading\n          ? 'Starting...'\n          : isListening\n          ? 'Listening...'\n          : 'Start SMS Listener'}\n      \u003c/button\u003e\n\n      \u003cbutton onClick={stopListener} disabled={!isListening}\u003e\n        Stop Listener\n      \u003c/button\u003e\n\n      {receivedOtp \u0026\u0026 \u003cp\u003eOTP: {receivedOtp}\u003c/p\u003e}\n      {error \u0026\u0026 \u003cp\u003eError: {error}\u003c/p\u003e}\n    \u003c/div\u003e\n  )\n}\n```\n\n#### Complete Example with Both Hooks\n\n```typescript\nimport React, { useState } from 'react'\nimport { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'\n\nconst SmsVerificationFlow = () =\u003e {\n  const [step, setStep] = useState\u003c'hash' | 'sms' | 'complete'\u003e('hash')\n  const [phoneNumber, setPhoneNumber] = useState('')\n\n  // Get app hash first\n  const {\n    hash,\n    loading: hashLoading,\n    error: hashError,\n  } = useGetHash({\n    onSuccess: (hash) =\u003e {\n      console.log('Ready to send SMS with hash:', hash)\n      setStep('sms')\n    },\n  })\n\n  // Listen for SMS\n  const { isListening, receivedOtp, startListener, stopListener } =\n    useOtpListener({\n      onOtpReceived: (otp) =\u003e {\n        console.log('Verification complete:', otp)\n        setStep('complete')\n        stopListener()\n      },\n    })\n\n  const sendSms = async () =\u003e {\n    if (!hash) return\n\n    // Send SMS with your backend API\n    await fetch('/api/send-sms', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        phoneNumber,\n        appHash: hash,\n      }),\n    })\n\n    // Start listening for SMS\n    startListener()\n  }\n\n  if (step === 'hash') {\n    return (\n      \u003cdiv\u003e\n        \u003cp\u003ePreparing SMS verification...\u003c/p\u003e\n        {hashLoading \u0026\u0026 \u003cp\u003eLoading...\u003c/p\u003e}\n        {hashError \u0026\u0026 \u003cp\u003eError: {hashError.message}\u003c/p\u003e}\n        {hash \u0026\u0026 \u003cp\u003eReady! Hash: {hash}\u003c/p\u003e}\n      \u003c/div\u003e\n    )\n  }\n\n  if (step === 'sms') {\n    return (\n      \u003cdiv\u003e\n        \u003cinput\n          type=\"tel\"\n          placeholder=\"Phone number\"\n          value={phoneNumber}\n          onChange={(e) =\u003e setPhoneNumber(e.target.value)}\n        /\u003e\n        \u003cbutton onClick={sendSms} disabled={!phoneNumber}\u003e\n          Send SMS\n        \u003c/button\u003e\n        {isListening \u0026\u0026 \u003cp\u003eWaiting for SMS...\u003c/p\u003e}\n      \u003c/div\u003e\n    )\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003e✅ Verification complete!\u003c/p\u003e\n      \u003cp\u003eOTP: {receivedOtp}\u003c/p\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Manual API (no hooks)\n\nIf you prefer direct control without hooks, use the module methods and event listeners:\n\n```typescript\nimport { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'\n\n// Get app signature hash (use the first one)\nconst getAppHash = async (): Promise\u003cstring | undefined\u003e =\u003e {\n  try {\n    const hashes = await AvasOtpAutofillModule.getHash()\n    console.log('App signature hashes:', hashes)\n    return hashes[0]\n  } catch (error) {\n    console.error('Error getting app hash:', error)\n  }\n}\n\n// Start listening for SMS and wire up events\nconst startSmsListener = async () =\u003e {\n  const subs = [] as import('expo-modules-core').EventSubscription[]\n\n  subs.push(\n    AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) =\u003e {\n      console.log('OTP received:', otp)\n      console.log('Full message:', message)\n      // ...verify with your backend\n    }),\n  )\n\n  subs.push(\n    AvasOtpAutofillModule.addListener('onTimeout', ({ message }) =\u003e {\n      console.log('SMS timeout:', message)\n    }),\n  )\n\n  subs.push(\n    AvasOtpAutofillModule.addListener('onError', ({ message, code }) =\u003e {\n      console.error('SMS error:', message, 'code:', code)\n    }),\n  )\n\n  await AvasOtpAutofillModule.startOtpListener()\n\n  // Return a cleanup function\n  return () =\u003e {\n    subs.forEach((s) =\u003e s.remove())\n    AvasOtpAutofillModule.stopSmsRetriever()\n  }\n}\n```\n\n#### Manual example (React component)\n\n```tsx\nimport React, { useRef, useState } from 'react'\nimport { Button, Text, View } from 'react-native'\nimport { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'\n\nexport const ManualSmsVerification = () =\u003e {\n  const [otp, setOtp] = useState\u003cstring | null\u003e(null)\n  const [message, setMessage] = useState\u003cstring | null\u003e(null)\n  const [isListening, setIsListening] = useState(false)\n  const cleanupRef = useRef\u003cnull | (() =\u003e void)\u003e(null)\n\n  const start = async () =\u003e {\n    if (isListening) return\n    setIsListening(true)\n    setOtp(null)\n    setMessage(null)\n\n    // Register listeners first\n    const subs = [\n      AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) =\u003e {\n        setOtp(otp ?? null)\n        setMessage(message)\n        setIsListening(false)\n        cleanup()\n      }),\n      AvasOtpAutofillModule.addListener('onTimeout', () =\u003e {\n        setIsListening(false)\n        cleanup()\n      }),\n      AvasOtpAutofillModule.addListener('onError', () =\u003e {\n        setIsListening(false)\n        cleanup()\n      }),\n    ]\n\n    const cleanup = () =\u003e {\n      subs.forEach((s) =\u003e s.remove())\n      AvasOtpAutofillModule.stopSmsRetriever()\n      cleanupRef.current = null\n    }\n\n    cleanupRef.current = cleanup\n    await AvasOtpAutofillModule.startOtpListener()\n  }\n\n  const stop = () =\u003e {\n    cleanupRef.current?.()\n    setIsListening(false)\n  }\n\n  return (\n    \u003cView\u003e\n      \u003cButton title={isListening ? 'Listening…' : 'Start SMS Listener'} onPress={start} disabled={isListening} /\u003e\n      {isListening \u0026\u0026 \u003cButton title=\"Stop\" onPress={stop} /\u003e}\n      {otp \u0026\u0026 \u003cText\u003eOTP: {otp}\u003c/Text\u003e}\n      {message \u0026\u0026 \u003cText\u003eMessage: {message}\u003c/Text\u003e}\n    \u003c/View\u003e\n  )\n}\n```\n\n## API Reference\n\n### React Hooks\n\n#### `useGetHash(options?: UseGetHashOptions): UseGetHashReturn`\n\nA React hook for managing app signature hash retrieval with automatic loading states and error handling.\n\n**Options:**\n\n```typescript\ninterface UseGetHashOptions {\n  onSuccess?: (value: string) =\u003e void\n  onError?: (error: Error) =\u003e void\n}\n```\n\n**Returns:**\n\n```typescript\ninterface UseGetHashReturn {\n  hash: string | null // The app signature hash\n  loading: boolean // Whether hash is being fetched\n  error: Error | null // Any error that occurred\n  refetch: () =\u003e Promise\u003cvoid\u003e // Function to refetch the hash\n}\n```\n\n**Example:**\n\n```typescript\nconst { hash, loading, error, refetch } = useGetHash({\n  onSuccess: (hash) =\u003e console.log('Hash:', hash),\n  onError: (error) =\u003e console.error('Error:', error),\n})\n```\n\n#### `useOtpListener(options?: UseOtpListenerOptions): UseOtpListenerReturn`\n\nA React hook for managing SMS OTP listening with automatic cleanup and state management.\n\n**Options:**\n\n```typescript\ninterface UseOtpListenerOptions {\n  onOtpReceived?: (otp: string, message: string) =\u003e void\n  onTimeout?: (message: string) =\u003e void\n  onError?: (error: string, code: number) =\u003e void\n}\n```\n\n**Returns:**\n\n```typescript\ninterface UseOtpListenerReturn {\n  isListening: boolean // Whether actively listening for SMS\n  loading: boolean // Whether starting/stopping listener\n  receivedOtp: string | null // Last received OTP\n  receivedMessage: string | null // Full SMS message\n  error: string | null // Any error message\n  startListener: () =\u003e Promise\u003cvoid\u003e // Start listening for SMS\n  stopListener: () =\u003e void // Stop listening and cleanup\n}\n```\n\n**Example:**\n\n```typescript\nconst { isListening, receivedOtp, startListener, stopListener } =\n  useOtpListener({\n    onOtpReceived: (otp, message) =\u003e {\n      console.log('OTP:', otp, 'Message:', message)\n    },\n  })\n```\n\n### Manual Methods\n\n#### `getOtp(): Promise\u003cboolean\u003e`\n\nStarts the SMS retriever and returns whether it was successfully started.\n\n```typescript\nconst success = await AvasOtpAutofill.getOtp()\n```\n\n#### `getHash(): Promise\u003cstring[]\u003e`\n\nReturns the app signature hashes needed for SMS verification.\n\n```typescript\nconst hashes = await AvasOtpAutofill.getHash()\n```\n\n#### `requestHint(): Promise\u003cstring\u003e`\n\nRequests a phone number hint (placeholder implementation).\n\n```typescript\nconst hint = await AvasOtpAutofill.requestHint()\n```\n\n#### `startOtpListener(): Promise\u003cboolean\u003e`\n\nStarts listening for SMS using the Android SMS Retriever API.\n\n```typescript\nawait AvasOtpAutofillModule.startOtpListener()\n```\n\n#### `addListener(eventName, listener): EventSubscription`\n\nAdds a listener for module events. Call before `startOtpListener()`.\n\n```typescript\nconst sub = AvasOtpAutofillModule.addListener('onSmsReceived', ({ otp, message }) =\u003e {\n  console.log('OTP:', otp, 'Message:', message)\n})\n```\n\n#### Cleanup\n\nUse the returned subscription to remove listeners, and stop the retriever when done.\n\n```typescript\nsub.remove()\nawait AvasOtpAutofillModule.stopSmsRetriever()\n```\n\n### Events\n\nThe module emits the following events:\n\n- `onSmsReceived`: When an SMS is received with OTP\n- `onTimeout`: When SMS retriever times out (after 5 minutes)\n- `onError`: When an error occurs\n\nEvent payloads:\n\n```ts\ntype SmsReceivedEventPayload = {\n  message: string\n  otp: string | null\n}\n\ntype TimeoutEventPayload = {\n  message: string\n}\n\ntype ErrorEventPayload = {\n  message: string\n  code: number\n}\n```\n\n## SMS Format Requirements\n\nFor the SMS Retriever API to work, the SMS message must:\n\n1. Contain a verification code (4-6 digits)\n2. Include your app's signature hash\n3. Be no longer than 140 bytes\n4. Contain a one-time code that the user has never seen before\n\n### Example SMS Format\n\n```\nYour verification code is: 123456\n\nFA+9qCX9VSu\n```\n\nWhere `FA+9qCX9VSu` is your app's signature hash.\n\n## Troubleshooting\n\n### Common Issues\n\n1. **SMS not received**: Ensure your SMS includes the correct app signature hash\n2. **Module not found**: Make sure the module is properly installed and linked\n3. **Timeout errors**: SMS Retriever has a 5-minute timeout limit\n4. **Hooks not updating**: Make sure you're using the hooks inside React components\n5. **Multiple listeners**: Use `stopListener()` before starting a new listener\n\n### Debug Mode\n\n#### Using Hooks for Debugging\n\n```typescript\nimport { useGetHash, useOtpListener } from '@avasapp/react-native-otp-autofill'\n\nconst DebugComponent = () =\u003e {\n  const { hash, loading, error } = useGetHash({\n    onSuccess: (hash) =\u003e console.log('✅ Hash loaded:', hash),\n    onError: (error) =\u003e console.error('❌ Hash error:', error),\n  })\n\n  const {\n    isListening,\n    receivedOtp,\n    receivedMessage,\n    error: smsError,\n  } = useOtpListener({\n    onOtpReceived: (otp, message) =\u003e {\n      console.log('📱 SMS received:', { otp, message })\n    },\n    onTimeout: (message) =\u003e {\n      console.log('⏰ SMS timeout:', message)\n    },\n    onError: (error, code) =\u003e {\n      console.error('❌ SMS error:', { error, code })\n    },\n  })\n\n  return (\n    \u003cdiv\u003e\n      \u003cp\u003eHash: {hash || 'Loading...'}\u003c/p\u003e\n      \u003cp\u003eListening: {isListening ? 'Yes' : 'No'}\u003c/p\u003e\n      \u003cp\u003eOTP: {receivedOtp || 'None'}\u003c/p\u003e\n      {error \u0026\u0026 \u003cp\u003eHash Error: {error.message}\u003c/p\u003e}\n      {smsError \u0026\u0026 \u003cp\u003eSMS Error: {smsError}\u003c/p\u003e}\n    \u003c/div\u003e\n  )\n}\n```\n\n#### Manual Event Debugging\n\n```typescript\nimport { AvasOtpAutofillModule } from '@avasapp/react-native-otp-autofill'\n\n// Listen to all events for debugging\nAvasOtpAutofillModule.addListener('onSmsReceived', (event) =\u003e {\n  console.log('📱 SMS received:', event)\n})\n\nAvasOtpAutofillModule.addListener('onTimeout', (event) =\u003e {\n  console.log('⏰ SMS timeout:', event)\n})\n\nAvasOtpAutofillModule.addListener('onError', (event) =\u003e {\n  console.log('❌ SMS error:', event)\n})\n```\n\n### Performance Tips\n\n1. **Use hooks at component level**: Don't call hooks conditionally or in loops\n2. **Clean up listeners**: Always call `stopListener()` when component unmounts\n3. **Avoid multiple hash fetches**: Use `refetch()` from `useGetHash` instead of creating new instances\n4. **Handle loading states**: Show loading indicators to improve user experience\n\n## Development\n\n### Building the Module\n\n```bash\n# Install dependencies\nbun install\n\n# Build the module\nbun run build\n\n# Clean build artifacts\nbun run clean\n\n# Run linting\nbun run lint\n\n# Run tests\nbun run test\n```\n\n### Publishing\n\nThis package is published to the npm registry. To publish a new version:\n\n1. **Update the version** in `package.json`\n2. **Build and publish**:\n   ```bash\n   bun run build\n   npm publish --access public\n   ```\n\n### Local Development\n\nFor local development and testing:\n\n```bash\n# Link the package locally\nbun link\n\n# In your test project\nbun link @avasapp/react-native-otp-autofill\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favas-app%2Freact-native-android-otp-autofill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favas-app%2Freact-native-android-otp-autofill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favas-app%2Freact-native-android-otp-autofill/lists"}