add shim for webview on web
This commit is contained in:
@@ -13,7 +13,6 @@ import Animated, {
|
|||||||
useFrameCallback,
|
useFrameCallback,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {WebView} from 'react-native-webview'
|
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {type AppBskyEmbedExternal} from '@atproto/api'
|
import {type AppBskyEmbedExternal} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
@@ -33,6 +32,7 @@ import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
|||||||
import {Fill} from '#/components/Fill'
|
import {Fill} from '#/components/Fill'
|
||||||
import {KeepAwake} from '#/components/KeepAwake'
|
import {KeepAwake} from '#/components/KeepAwake'
|
||||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||||
|
import {WebView} from '#/components/WebView'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
|
|
||||||
interface ShouldStartLoadRequest {
|
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 {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 {type SignupState} from '#/screens/Signup/state'
|
||||||
|
import {WebView, type WebViewNavigation} from '#/components/WebView'
|
||||||
|
|
||||||
const ALLOWED_HOSTS = [
|
const ALLOWED_HOSTS = [
|
||||||
'bsky.social',
|
'bsky.social',
|
||||||
@@ -54,8 +53,8 @@ export function CaptchaWebView({
|
|||||||
|
|
||||||
const wasSuccessful = useRef(false)
|
const wasSuccessful = useRef(false)
|
||||||
|
|
||||||
const onShouldStartLoadWithRequest = (event: ShouldStartLoadRequest) => {
|
const onShouldStartLoadWithRequest = (evt: WebViewNavigation) => {
|
||||||
const urlp = new URL(event.url)
|
const urlp = new URL(evt.url)
|
||||||
return ALLOWED_HOSTS.includes(urlp.host)
|
return ALLOWED_HOSTS.includes(urlp.host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user