I had the same problem, but I found the solution. So, how it works:
We have special static "Captcha" web page, hosted on domain, that authorized on our Firebase project. It simply shows firebase.auth.RecaptchaVerifier. User resolves captcha and it gives token string from response of callback.
On application login screen we show WebBrowser with "Captcha" page and listening url change event by Linking methods. On new url, we extract token string from it.
Then we create fake firebase.auth.ApplicationVerifier object with token and pass it to firebase.auth().signInWithPhoneNumber (with phone number). SMS code will be sent.
I wrote tested simplest code below. You can directly "copy-paste" it. Just add firebase config (this config must be same for both) and set correct "Captcha" page url. Don't forget that phone must be entered in international format. In this code "Captcha" page hosted on firebase hosting, so it automatically initializing by including init.js and authorized by default.
"Captcha" page (hosted on firebase hosting):
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>Entering captcha</title>
</head>
<body>
<p style="text-align: center; font-size: 1.2em;">Please, enter captcha for continue<p/>
<script src="/__/firebase/5.3.1/firebase-app.js"></script>
<script src="/__/firebase/5.3.1/firebase-auth.js"></script>
<script src="/__/firebase/init.js"></script>
<script>
function getToken(callback) {
var container = document.createElement('div');
container.id = 'captcha';
document.body.appendChild(container);
var captcha = new firebase.auth.RecaptchaVerifier('captcha', {
'size': 'normal',
'callback': function(token) {
callback(token);
},
'expired-callback': function() {
callback('');
}
});
captcha.render().then(function() {
captcha.verify();
});
}
function sendTokenToApp(token) {
var baseUri = decodeURIComponent(location.search.replace(/^\?appurl\=/, ''));
location.href = baseUri + '/?token=' + encodeURIComponent(token);
}
document.addEventListener('DOMContentLoaded', function() {
getToken(sendTokenToApp);
});
</script>
</body>
</html>
Auth screen in expo project:
import * as React from 'react'
import {Text, View, ScrollView, TextInput, Button} from 'react-native'
import {Linking, WebBrowser} from 'expo'
import firebase from 'firebase/app'
import 'firebase/auth'
const captchaUrl = `https://my-firebase-hosting/captcha-page.html?appurl=${Linking.makeUrl('')}`
firebase.initializeApp({
//firebase config
});
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
user: undefined,
phone: '',
confirmationResult: undefined,
code: ''
}
firebase.auth().onAuthStateChanged(user => {
this.setState({user})
})
}
onPhoneChange = (phone) => {
this.setState({phone})
}
onPhoneComplete = async () => {
let token = null
const listener = ({url}) => {
WebBrowser.dismissBrowser()
const tokenEncoded = Linking.parse(url).queryParams['token']
if (tokenEncoded)
token = decodeURIComponent(tokenEncoded)
}
Linking.addEventListener('url', listener)
await WebBrowser.openBrowserAsync(captchaUrl)
Linking.removeEventListener('url', listener)
if (token) {
const {phone} = this.state
//fake firebase.auth.ApplicationVerifier
const captchaVerifier = {
type: 'recaptcha',
verify: () => Promise.resolve(token)
}
try {
const confirmationResult = await firebase.auth().signInWithPhoneNumber(phone, captchaVerifier)
this.setState({confirmationResult})
} catch (e) {
console.warn(e)
}
}
}
onCodeChange = (code) => {
this.setState({code})
}
onSignIn = async () => {
const {confirmationResult, code} = this.state
try {
await confirmationResult.confirm(code)
} catch (e) {
console.warn(e)
}
this.reset()
}
onSignOut = async () => {
try {
await firebase.auth().signOut()
} catch (e) {
console.warn(e)
}
}
reset = () => {
this.setState({
phone: '',
phoneCompleted: false,
confirmationResult: undefined,
code: ''
})
}
render() {
if (this.state.user)
return (
<ScrollView style={{padding: 20, marginTop: 20}}>
<Text>You signed in</Text>
<Button
onPress={this.onSignOut}
title="Sign out"
/>
</ScrollView>
)
if (!this.state.confirmationResult)
return (
<ScrollView style={{padding: 20, marginTop: 20}}>
<TextInput
value={this.state.phone}
onChangeText={this.onPhoneChange}
keyboardType="phone-pad"
placeholder="Your phone"
/>
<Button
onPress={this.onPhoneComplete}
title="Next"
/>
</ScrollView>
)
else
return (
<ScrollView style={{padding: 20, marginTop: 20}}>
<TextInput
value={this.state.code}
onChangeText={this.onCodeChange}
keyboardType="numeric"
placeholder="Code from SMS"
/>
<Button
onPress={this.onSignIn}
title="Sign in"
/>
</ScrollView>
)
}
}