Hi,
I added Capacitor to my React app following the guide Ionic & Capacitor (React).
I encountered a problem that seems to be the same as the one described in this topic, which does not seem to be offering a solution…
In Xcode console however, the logs just say:
⚡️ To Native -> Browser open 97869726
⚡️ TO JS undefined
And the webview is not closed automatically. If I close it manually and try to login again, I just see a blank screen.
By the way, my config switches from Web to Capacitor whenever necessary. The Web config works fine…
package.json
{
"dependencies": {
"@auth0/auth0-react": "^2.2.4",
"@capacitor/android": "^6.1.1",
"@capacitor/app": "^6.0.0",
"@capacitor/browser": "^6.0.1",
"@capacitor/core": "^6.1.1",
"@capacitor/ios": "^6.1.1"
},
"devDependencies": {
"@capacitor/cli": "^6.1.1",
},
"proxy": "http://localhost:4000",
"engines": {
"node": "20.x",
"yarn": "1.x"
}
}
src/contexts/auth/auth.js
import React from 'react'
// React Router hook for routing.
import { useNavigate } from 'react-router-dom'
import { Auth0Provider } from '@auth0/auth0-react'
import { Capacitor } from '@capacitor/core'
import { domain, clientId, audience, redirectUri } from './authConfig'
const Auth0ProviderWithNavigate = ({ children }) => {
const isNative = Capacitor.isNativePlatform()
const navigate = useNavigate()
const onRedirectCallback = (appState) => {
navigate(appState?.returnTo || window.location.pathname)
}
// if (!(domain && clientId && redirectUri)) {
// return null
// }
return (
<Auth0Provider
domain={domain}
clientId={clientId}
useRefreshTokens={isNative ? true : undefined}
useRefreshTokensFallback={isNative ? false : undefined}
authorizationParams={{ audience, redirect_uri: redirectUri }}
onRedirectCallback={!isNative && onRedirectCallback}
>
{children}
</Auth0Provider>
)
}
export default Auth0ProviderWithNavigate
src/contexts/auth/authConfig.js
import { Capacitor } from '@capacitor/core'
const isNative = Capacitor.isNativePlatform()
const PACKAGE_ID = 'REDACTED'
export const domain = process.env.REACT_APP_AUTH0_DOMAIN
export const redirectUri = `${
isNative ? `${PACKAGE_ID}://${domain}/capacitor/${PACKAGE_ID}` : process.env.REACT_APP_HOST_URL
}/callback`
const authConfig = {
domain,
clientId: process.env.REACT_APP_AUTH0_CLIENT_ID,
audience: process.env.REACT_APP_AUTH0_AUDIENCE,
redirectUri,
}
export const { clientId, audience } = authConfig
src/index.js
import React, { Suspense } from 'react'
// Replaces ReactDOM.createRoot.
import { createRoot } from 'react-dom/client'
// Import i18n (needs to be bundled).
import './contexts/i18n/i18n'
import { Provider } from 'react-redux'
import store from './store/store'
import { BrowserRouter as Router } from 'react-router-dom'
import Auth0Provider from './contexts/auth/auth'
import CableProvider from './contexts/cable/cable'
import L10n from './contexts/l10n/l10n'
import Theme from './contexts/theme/theme'
import { App as AntApp } from 'antd'
import LoadingView from './components/loading/LoadingView'
import App from './components/app/App'
import reportWebVitals from './reportWebVitals'
// As of React 18.
const root = createRoot(document.getElementById('root'))
root.render(
// Trigger React’s Suspense if translations are still being loaded by the `i18next-http-backend`.
<Suspense fallback={<LoadingView />}>
<Provider store={store}>
<Router>
<Auth0Provider>
<CableProvider>
<Theme>
<L10n>
<AntApp>
<App />
</AntApp>
</L10n>
</Theme>
</CableProvider>
</Auth0Provider>
</Router>
</Provider>
</Suspense>,
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
src/components/app/App.jsx
// React Effect hook.
import React, { useEffect } from 'react'
// Auth0 hook for authentication.
import { useAuth0 } from '@auth0/auth0-react'
import { Capacitor } from '@capacitor/core'
import { App as CapApp } from '@capacitor/app'
import { Browser } from '@capacitor/browser'
import { Routes, Route, Navigate } from 'react-router-dom'
import AuthenticationGuard from '../shared/navigation/AuthenticationGuard'
import LoadingView from '../loading/LoadingView'
const App = () => {
const { handleRedirectCallback, isLoading, isAuthenticated, user } = useAuth0()
const isNative = Capacitor.isNativePlatform()
const shouldHandleRedirectCallback = isNative
useEffect(() => {
if (shouldHandleRedirectCallback) return
// Handle the `appUrlOpen` event and call `handleRedirectCallback`.
CapApp.addListener('appUrlOpen', async ({ url }) => {
if (url.includes('state') && (url.includes('code') || url.includes('error'))) {
await handleRedirectCallback(url)
}
// No-op on Android.
await Browser.close()
})
}, [shouldHandleRedirectCallback, handleRedirectCallback])
if (isLoading) {
// Loading auth...
return <LoadingView />
}
return (
<div>
<Routes>
</Routes>
</div>
)
}
export default App
src/utils/auth.js
import { Capacitor } from '@capacitor/core'
import { Browser } from '@capacitor/browser'
import { redirectUri } from '../contexts/auth/authConfig'
const isNative = Capacitor.isNativePlatform()
const signInCap = async (loginWithRedirect, opts) => {
await loginWithRedirect({
async openUrl(url) {
await Browser.open({ url, windowName: '_self' })
},
...opts,
})
}
const signInWeb = async (loginWithRedirect, opts) =>
await loginWithRedirect({
appState: { returnTo: '/' },
...opts,
})
export const signIn = (loginWithRedirect, opts = {}) =>
isNative ? signInCap(loginWithRedirect, opts) : signInWeb(loginWithRedirect, opts)
export const signUp = (loginWithRedirect) =>
signIn(loginWithRedirect, { authorizationParams: { screen_hint: 'signup' } })
const signOutCap = async (logout) => {
await logout({
logoutParams: { returnTo: redirectUri },
async openUrl(url) {
await Browser.open({ url, windowName: '_self' })
},
})
}
const signOutWeb = (logout) =>
logout({
logoutParams: { returnTo: window.location.origin },
})
export const signOut = (logout) => (isNative ? signOutCap(logout) : signOutWeb(logout))
ios/App/App/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>REDACTED</string>
<key>CFBundleURLSchemes</key>
<array>
<string>REDACTED</string>
</array>
</dict>
</array>
</dict>
</plist>