add shim for webview on web
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from 'react-native-webview'
|
||||
export {default} from 'react-native-webview'
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user