add shim for webview on web

This commit is contained in:
Samuel Newman
2026-05-22 11:16:07 +03:00
parent 743dbe9db6
commit 39632ee250
4 changed files with 116 additions and 5 deletions
@@ -13,7 +13,6 @@ import Animated, {
useFrameCallback,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {WebView} from 'react-native-webview'
import {Image} from 'expo-image'
import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/core/macro'
@@ -33,6 +32,7 @@ import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
import {Fill} from '#/components/Fill'
import {KeepAwake} from '#/components/KeepAwake'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {WebView} from '#/components/WebView'
import {IS_NATIVE} from '#/env'
interface ShouldStartLoadRequest {
+2
View File
@@ -0,0 +1,2 @@
export * from 'react-native-webview'
export {default} from 'react-native-webview'
+110
View File
@@ -0,0 +1,110 @@
import {type Ref, useEffect, useImperativeHandle, useRef} from 'react'
import {type StyleProp, StyleSheet, type ViewStyle} from 'react-native'
// @ts-expect-error untyped
import {unstable_createElement} from 'react-native-web'
type WebViewSource = {uri?: string; html?: string}
type WebViewMessageEvent = {
nativeEvent: {data: string; origin?: string}
}
export type WebViewHandle = {
postMessage: (message: string, targetOrigin?: string) => void
}
type WebViewProps = {
ref?: Ref<WebViewHandle>
source: WebViewSource
style?: StyleProp<ViewStyle>
title?: string
scrollEnabled?: boolean
injectedJavaScript?: string
onLoad?: () => void
onMessage?: (event: WebViewMessageEvent) => void
// Accepted for API parity with react-native-webview but ignored on web:
// these don't translate meaningfully to iframes.
javaScriptEnabled?: boolean
mediaPlaybackRequiresUserAction?: boolean
allowsInlineMediaPlayback?: boolean
allowsFullscreenVideo?: boolean
bounces?: boolean
nestedScrollEnabled?: boolean
setSupportMultipleWindows?: boolean
onShouldStartLoadWithRequest?: (event: {url: string}) => boolean
onNavigationStateChange?: (event: {url: string}) => void
onError?: (event: {nativeEvent: unknown}) => void
onHttpError?: (event: {nativeEvent: unknown}) => void
}
function injectScript(html: string | undefined, script: string | undefined) {
if (!script || !html) return html
return html.replace('</body>', `<script>${script}</script></body>`)
}
export function WebView({
ref,
source,
style,
title,
scrollEnabled = true,
injectedJavaScript,
onLoad,
onMessage,
}: WebViewProps) {
const frameRef = useRef<HTMLIFrameElement | null>(null)
useImperativeHandle(ref, () => ({
postMessage(message, targetOrigin = '*') {
frameRef.current?.contentWindow?.postMessage(message, targetOrigin)
},
}))
useEffect(() => {
if (!onMessage) return
const handler = (event: MessageEvent) => {
onMessage({
nativeEvent: {
data:
typeof event.data === 'string' ? event.data : String(event.data),
origin: event.origin,
},
})
}
window.addEventListener('message', handler, true)
return () => window.removeEventListener('message', handler, true)
}, [onMessage])
const flat = StyleSheet.flatten(style) as ViewStyle | undefined
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, react-hooks/refs
return unstable_createElement('iframe', {
title,
ref: frameRef,
src: source.uri,
srcDoc: injectScript(source.html, injectedJavaScript),
style: StyleSheet.flatten([
styles.iframe,
!scrollEnabled && styles.noScroll,
style,
]),
width: flat?.width,
height: flat?.height,
allowFullScreen: true,
frameBorder: '0',
onLoad,
})
}
export default WebView
const styles = StyleSheet.create({
iframe: {
width: '100%',
height: '100%',
borderWidth: 0,
},
noScroll: {
overflow: 'hidden',
},
})
@@ -1,8 +1,7 @@
import {useEffect, useMemo, useRef} from 'react'
import {WebView, type WebViewNavigation} from 'react-native-webview'
import {type ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes'
import {type SignupState} from '#/screens/Signup/state'
import {WebView, type WebViewNavigation} from '#/components/WebView'
const ALLOWED_HOSTS = [
'bsky.social',
@@ -54,8 +53,8 @@ export function CaptchaWebView({
const wasSuccessful = useRef(false)
const onShouldStartLoadWithRequest = (event: ShouldStartLoadRequest) => {
const urlp = new URL(event.url)
const onShouldStartLoadWithRequest = (evt: WebViewNavigation) => {
const urlp = new URL(evt.url)
return ALLOWED_HOSTS.includes(urlp.host)
}