sms-otp-autofill
v0.2.1
Published
Cross-platform SMS OTP auto-read. Web (WebOTP), iOS (QuickType), Android (User Consent), msite.
Downloads
1,069
Maintainers
Readme
sms-otp-autofill
Cross-platform SMS OTP auto-read for React Native (Expo) and web. One hook, useOtpAutofill, resolves the right strategy per platform.
Platform support
| Platform | Strategy | Needs |
|----------|----------|-------|
| Web / msite | WebOTP API (navigator.credentials.get({otp})) with autocomplete fallback | HTTPS, Chromium-based browser |
| Android | SMS User Consent API (one-tap consent dialog), native Expo module | Expo / dev build, Google Play Services |
| iOS | textContentType="oneTimeCode" (QuickType bar) via inputProps | spread inputProps on the input |
Metro resolves useOtpAutofill.native.ts on iOS/Android and useOtpAutofill.ts (web) elsewhere. No native module is shipped for iOS; iOS autofill is handled entirely by inputProps.
Install
npm install sms-otp-autofillFor Android, the package is an Expo module (autolinked). Rebuild the native app after install:
npx expo prebuild --platform android
npx expo run:androidNo RECEIVE_SMS permission is required. The User Consent API shows a system dialog instead.
Usage
Web / msite
Spread inputProps onto the OTP input. WebOTP fills it on a matching SMS.
import { useOtpAutofill } from "sms-otp-autofill";
function OtpField() {
const [otp, setOtp] = useState("");
const { inputProps } = useOtpAutofill({
length: 6,
enabled: true,
onCodeReceived: (code) => setOtp(code),
onError: (e) => console.warn(e),
});
return <input {...inputProps} value={otp} onChange={(e) => setOtp(e.target.value)} />;
}React Native (Android)
The code arrives through onCodeReceived. No input spread is needed; fill your cells from the callback. iOS still benefits from spreading inputProps.
import { useOtpAutofill } from "sms-otp-autofill";
function OtpModal({ visible }: { visible: boolean }) {
const [otp, setOtp] = useState("");
useOtpAutofill({
length: 6,
enabled: visible, // start listening when the OTP screen is shown
onCodeReceived: (code) => setOtp(code),
onError: () => {}, // silent: manual entry stays available
});
// render your OTP cells bound to `otp`
}API
useOtpAutofill(options)
| Option | Type | Default | Notes |
|--------|------|---------|-------|
| onCodeReceived | (code: string) => void | required | called with the validated OTP |
| length | number | 6 | expected OTP length, used for parsing and validation |
| enabled | boolean | true | starts/stops the listener; toggle with modal visibility |
| onError | (error: Error) => void | none | parse failures, timeouts, native errors |
| senderPhoneNumber | string | none (any sender) | Android only, restricts the User Consent sender |
| timeoutMs | number | 300000 (5 min) | listener auto-stops and errors after this |
| rejectAllZeros | boolean | true | rejects codes like 000000 |
Returns UseOtpAutofillResult:
| Field | Type | Notes |
|-------|------|-------|
| inputProps | OtpInputProps | spread on the input (web autocomplete, iOS textContentType) |
| supported | boolean | true if the active platform can auto-read (WebOTP on web, User Consent on Android) |
| start | () => void | manual start (the enabled effect calls this for you) |
| stop | () => void | manual stop |
Subpath exports
import { logOtpEvent, getOtpLogs, clearOtpLogs } from "sms-otp-autofill/logging";
import { copyOtpToClipboard, buildCopyMessage } from "sms-otp-autofill/copy";
import { detectSetup, SETUP_WIZARD } from "sms-otp-autofill/setup";
import { extractOtp } from "sms-otp-autofill";loggingkeeps a rolling buffer of the last 50 OTP events inlocalStorage(web) and mirrors to console.copywrites an OTP to the clipboard with a textarea fallback.setupdetects Expo vs pure-JS and returns setup instructions.extractOtp(message, length)pulls the code from an SMS body.
Docs
- QUICKSTART.md — 5-step integration checklist
- STRUCTURE.md — architecture and file layout
- PUBLISHING.md — build and NPM publish guide
- SEPARATION_CHECKLIST.md — extract into its own repo
License
MIT
