Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paul/edge provider extra tab #4409

Merged
merged 2 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions src/components/scenes/ExtraTabScene.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import * as React from 'react'
import { WebView } from 'react-native-webview'
import WebView from 'react-native-webview'

import { lstrings } from '../../locales/strings'
import { config } from '../../theme/appConfig'
import { GuiPlugin } from '../../types/GuiPluginTypes'
import { EdgeSceneProps } from '../../types/routerTypes'
import { SceneWrapper } from '../common/SceneWrapper'
import { EdgeProviderComponent } from '../themed/EdgeProviderComponent'

export function ExtraTabScene() {
interface Props extends EdgeSceneProps<'extraTab'> {}

export function ExtraTabScene(props: Props) {
if (config.extraTab == null) throw new Error('No extra tab config info')
const { webviewUrl } = config.extraTab
const { tabTitleKey, tabType, webviewUrl } = config.extraTab
const { navigation } = props

if (tabType === 'edgeProvider') {
const plugin: GuiPlugin = {
pluginId: 'extraTab',
storeId: 'extraTab',
baseUri: webviewUrl,
displayName: lstrings[tabTitleKey]
}

return (
<SceneWrapper background="theme" hasTabs>
<EdgeProviderComponent plugin={plugin} navigation={navigation} />
</SceneWrapper>
)
}

return (
<SceneWrapper background="legacy" avoidKeyboard>
<SceneWrapper background="legacy" hasTabs avoidKeyboard>
<WebView
allowFileAccess
allowUniversalAccessFromFileURLs
Expand Down
184 changes: 2 additions & 182 deletions src/components/scenes/GuiPluginViewScene.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import { uncleaner } from 'cleaners'
import * as React from 'react'
import { NativeSyntheticEvent, Platform } from 'react-native'
import { WebView, WebViewMessageEvent } from 'react-native-webview'

import { EdgeProviderServer } from '../../controllers/edgeProvider/EdgeProviderServer'
import { javascript } from '../../controllers/edgeProvider/injectThisInWebView'
import { methodCleaners } from '../../controllers/edgeProvider/types/edgeProviderCleaners'
import { asRpcCall, rpcErrorCodes, RpcReturn } from '../../controllers/edgeProvider/types/jsonRpcCleaners'
import { useAsyncEffect } from '../../hooks/useAsyncEffect'
import { useHandler } from '../../hooks/useHandler'
import { GuiPlugin } from '../../types/GuiPluginTypes'
import { useDispatch, useSelector } from '../../types/reactRedux'
import { EdgeSceneProps } from '../../types/routerTypes'
import { UriQueryMap } from '../../types/WebTypes'
import { getTokenId } from '../../util/CurrencyInfoHelpers'
import { makePluginUri } from '../../util/GuiPluginTools'
import { bestOfPlugins } from '../../util/ReferralHelpers'
import { SceneWrapper } from '../common/SceneWrapper'
import { setPluginScene } from '../navigation/GuiPluginBackButton'
import { showToast } from '../services/AirshipInstance'
import { requestPermissionOnSettings } from '../services/PermissionsManager'
import { EdgeProviderComponent } from '../themed/EdgeProviderComponent'

export interface PluginViewParams {
// The GUI plugin we are showing the user:
Expand All @@ -32,178 +17,13 @@ export interface PluginViewParams {

interface Props extends EdgeSceneProps<'pluginView' | 'pluginViewBuy' | 'pluginViewSell'> {}

interface WebViewEvent {
canGoBack: boolean
}

export function GuiPluginViewScene(props: Props): JSX.Element {
const { route, navigation } = props
const { deepPath, deepQuery, plugin } = route.params

const {
displayName,
mandatoryPermissions = false,
needsCountryCode = false,
originWhitelist = ['file://*', 'https://*', 'http://*', 'edge://*'],
permissions = [],
pluginId
} = plugin

// Redux stuff:
const dispatch = useDispatch()
const account = useSelector(state => state.core.account)
const disklet = useSelector(state => state.core.disklet)
const accountPlugins = useSelector(state => state.account.referralCache.accountPlugins)
const accountReferral = useSelector(state => state.account.accountReferral)
const selectedWalletId = useSelector(state => state.ui.wallets.selectedWalletId)
const selectedCurrencyCode = useSelector(state => state.ui.wallets.selectedCurrencyCode)
const countryCode = useSelector(state => state.ui.settings.countryCode)

// Get the promo information:
const { promoCode, promoMessage } = React.useMemo(() => {
const activePlugins = bestOfPlugins(accountPlugins, accountReferral, undefined)
return {
promoCode: activePlugins.promoCodes[pluginId],
promoMessage: activePlugins.promoMessages[pluginId]
}
}, [accountPlugins, accountReferral, pluginId])

// Make sure we have the permissions the plugin requires:
useAsyncEffect(async () => {
for (const permission of permissions) {
const deniedPermission = await requestPermissionOnSettings(disklet, permission, displayName, mandatoryPermissions)
if (deniedPermission) {
navigation.goBack()
return
}
}

// Now show the promo message, if we have one:
if (promoMessage != null) showToast(promoMessage)
}, [displayName, mandatoryPermissions, navigation, permissions, promoMessage, disklet])

// Sign up for back-button events:
const webView = React.useRef<WebView>(null)
const canGoBack = React.useRef<boolean>(false)
React.useEffect(() => {
setPluginScene({
goBack() {
if (webView.current == null || !canGoBack.current) {
navigation.goBack()
return false
}
webView.current.goBack()
return true
}
})
}, [navigation])

const handleLoadProgress = useHandler((event: NativeSyntheticEvent<WebViewEvent>) => {
console.log('Plugin navigation: ', event.nativeEvent)
canGoBack.current = event.nativeEvent.canGoBack
})

const handleNavigationStateChange = useHandler((event: WebViewEvent) => {
console.log('Plugin navigation: ', event)
canGoBack.current = event.canGoBack
})

// Mechanism to force-restart the webview:
const [webViewKey, setWebViewKey] = React.useState(0)
const reloadWebView = useHandler(() => setWebViewKey(webViewKey + 1))

// Build our EdgeProvider instance one time:
const [edgeProvider] = React.useState(() => {
const selectedWallet = account.currencyWallets[selectedWalletId]
const selectedTokenId = selectedWallet == null ? undefined : getTokenId(account, selectedWallet.currencyInfo.pluginId, selectedCurrencyCode)
return new EdgeProviderServer({
account,
dispatch,
navigation,
plugin,
reloadWebView,
selectedTokenId,
selectedWallet,
deepLink: {
deepPath,
deepQuery: needsCountryCode ? { ...deepQuery, countryCode } : deepQuery,
promoCode
}
})
})

const handleMessage = useHandler((event: WebViewMessageEvent) => {
try {
const clean = asRpcCall(JSON.parse(event.nativeEvent.data))
const method = clean.method as keyof typeof methodCleaners
const cleaners = methodCleaners[method]
if (cleaners == null) return

const { asParams, asReturn } = cleaners
const wasReturn = uncleaner<any>(asReturn)

const f: (...args: any[]) => Promise<any> = edgeProvider[method]
f.apply(edgeProvider, asParams(clean.params))
.then(out => {
const message: RpcReturn = {
error: undefined,
id: clean.id,
jsonrpc: '2.0',
result: wasReturn(out)
}
const js = `window.edgeProviderBridge.postReturn(${JSON.stringify(message)})`
webView.current?.injectJavaScript(js)
})
.catch(error => {
const message: RpcReturn = {
error: {
code: rpcErrorCodes.unknown,
data: undefined,
message: error.message
},
id: clean.id,
jsonrpc: '2.0',
result: undefined
}
const js = `window.edgeProviderBridge.postReturn(${JSON.stringify(message)})`
webView.current?.injectJavaScript(js)
})
} catch (e) {
// We got some sort of invalid request
}
})

const uri = React.useMemo<string>(() => {
return makePluginUri(plugin, {
deepPath,
deepQuery,
promoCode
})
}, [deepPath, deepQuery, plugin, promoCode])

const userAgent =
Platform.OS === 'android'
? 'Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; SCH-I535 Build/KOT49H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
: 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'

return (
<SceneWrapper background="theme" hasTabs={route.name !== 'pluginView'}>
<WebView
allowFileAccess
allowUniversalAccessFromFileURLs
geolocationEnabled
injectedJavaScript={javascript}
javaScriptEnabled
key={`webView${webViewKey}`}
mediaPlaybackRequiresUserAction={false}
originWhitelist={originWhitelist}
ref={webView}
source={{ uri }}
userAgent={userAgent + ' hasEdgeProvider edge/app.edge.'}
onLoadProgress={handleLoadProgress}
onMessage={handleMessage}
onNavigationStateChange={handleNavigationStateChange}
/>
<EdgeProviderComponent plugin={plugin} deepPath={deepPath} deepQuery={deepQuery} navigation={navigation} />
</SceneWrapper>
)
}
Loading
Loading