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

[react-native] Refactor "QuilttConnector" for better visibility #266

Merged
merged 5 commits into from
Jun 18, 2024
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ jobs:
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: 9

- name: Setup Node with ${{ matrix.node-version }}
uses: actions/setup-node@v4
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/tests-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ jobs:
node-version-file: '.nvmrc'

- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: 9

- name: Install dependencies
run: pnpm install
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.12.2
v20.14.0
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,5 @@
"vite-tsconfig-paths": "4.3.2",
"vitest": "1.5.2",
"vitest-react-native": "0.1.5"
},
"packageManager": "[email protected]"
}
}
1 change: 0 additions & 1 deletion packages/react-native/declarations.d.ts

This file was deleted.

7 changes: 5 additions & 2 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@
"dependencies": {
"@honeybadger-io/core": "6.6.0",
"@quiltt/core": "workspace:*",
"@quiltt/react": "workspace:*"
"@quiltt/react": "workspace:*",
"base-64": "1.0.0",
"react-native-url-polyfill": "2.0.0",
"react-native-webview": "13.10.2"
zubairaziz marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@apollo/client": "3.9.9",
"@trivago/prettier-plugin-sort-imports": "4.1.1",
"@types/base-64": "0.1.0",
"@types/base-64": "1.0.2",
"@types/node": "20.12.7",
"@types/react": "18.2.73",
"@types/react-native": "0.72.5",
Expand Down
90 changes: 55 additions & 35 deletions packages/react-native/src/components/QuilttConnector.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

// React Native's URL implementation is incomplete
// https://github.com/facebook/react-native/issues/16434
import { URL } from 'react-native-url-polyfill'
import { URL } from 'react-native-url-polyfill' // https://github.com/facebook/react-native/issues/16434
import { WebView } from 'react-native-webview'
import type { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'
import { Linking, Platform } from 'react-native'

import {
ConnectorSDKCallbackMetadata,
Expand All @@ -13,12 +11,56 @@ import {
useQuilttSession,
} from '@quiltt/react'

import { getErrorMessage, ErrorReporter } from '../utils'
import { version } from '../version'
import { AndroidSafeAreaView } from './AndroidSafeAreaView'
import { ErrorScreen } from './ErrorScreen'
import { LoadingScreen } from './LoadingScreen'
import { checkConnectorUrl, handleOAuthUrl } from '../utils'
import type { PreFlightCheck } from '../utils'

const errorReporter = new ErrorReporter(`${Platform.OS} ${Platform.Version}`)
const PREFLIGHT_RETRY_COUNT = 3

export type PreFlightCheck = {
checked: boolean
error?: string
}

export const checkConnectorUrl = async (
connectorUrl: string,
retryCount = 0
): Promise<PreFlightCheck> => {
let responseStatus
let error
try {
const response = await fetch(connectorUrl)
if (!response.ok) {
responseStatus = response.status
throw new Error(`The URL ${connectorUrl} is not routable.`)
}
console.log(`The URL ${connectorUrl} is routable.`)
return { checked: true }
} catch (e) {
error = e
console.error(`An error occurred while checking the connector URL: ${error}`)

if (retryCount < PREFLIGHT_RETRY_COUNT) {
const delay = 50 * Math.pow(2, retryCount)
await new Promise((resolve) => setTimeout(resolve, delay))
console.log(`Retrying... Attempt number ${retryCount + 1}`)
return checkConnectorUrl(connectorUrl, retryCount + 1)
}
const errorMessage = getErrorMessage(responseStatus, error as Error)
const errorToSend = (error as Error) || new Error(errorMessage)
const context = { connectorUrl, responseStatus }
if (responseStatus !== 404) await errorReporter.send(errorToSend, context)
return { checked: true, error: errorMessage }
}
}

export const handleOAuthUrl = (oauthUrl: URL | string) => {
console.log(`handleOAuthUrl - Opening URL - ${oauthUrl.toString()}`)
Linking.openURL(oauthUrl.toString())
}

type QuilttConnectorProps = {
testId?: string
Expand All @@ -43,17 +85,20 @@ const QuilttConnector = ({
}: QuilttConnectorProps) => {
const webViewRef = useRef<WebView>(null)
const { session } = useQuilttSession()

const encodedOAuthRedirectUrl = useMemo(
() => encodeURIComponent(oauthRedirectUrl),
[oauthRedirectUrl]
)

const connectorUrl = useMemo(() => {
const url: URL = new URL(`https://${connectorId}.quiltt.app`)
const url = new URL(`https://${connectorId}.quiltt.app`)
url.searchParams.append('mode', 'webview')
url.searchParams.append('oauth_redirect_url', encodedOAuthRedirectUrl)
url.searchParams.append('agent', `react-native-${version}`)
return url.toString()
}, [connectorId, encodedOAuthRedirectUrl])

const [preFlightCheck, setPreFlightCheck] = useState<PreFlightCheck>({ checked: false })

useEffect(() => {
Expand Down Expand Up @@ -86,35 +131,9 @@ const QuilttConnector = ({
webViewRef.current?.injectJavaScript(script)
}, [connectionId, connectorId, institution, session?.token])

// urlAllowList & shouldRender ensure we are only rendering Quiltt, MX and Plaid content in Webview
// For other urls, we assume those are bank urls, which need to be handled in external browser.
// TODO: Need to regroup on this and figure out a better way to handle a URL allow list
// const urlAllowList = useMemo(
// () => [
// 'quiltt.io',
// 'quiltt.app',
// 'quiltt.dev',
// 'moneydesktop.com',
// 'plaid.com',
// 'https://cdn.plaid.com/link',
// 'https://www.google.com/recaptcha',
// 'https://challenges.cloudflare.com',
// 'https://api.stripe.com',
// 'https://cdn.jsdelivr.net',
// 'https://auth0.com',
// ],
// []
// )

const isQuilttEvent = useCallback((url: URL) => url.protocol === 'quilttconnector:', [])

const shouldRender = useCallback(
(url: URL) => {
if (isQuilttEvent(url)) return false
return url.protocol === 'https:'
zubairaziz marked this conversation as resolved.
Show resolved Hide resolved
},
[isQuilttEvent]
)
const shouldRender = useCallback((url: URL) => !isQuilttEvent(url), [isQuilttEvent])

const clearLocalStorage = () => {
const script = 'localStorage.clear();'
Expand Down Expand Up @@ -193,14 +212,15 @@ const QuilttConnector = ({
)

if (!preFlightCheck.checked) return <LoadingScreen testId="loading-screen" />
if (preFlightCheck.error)
if (preFlightCheck.error) {
return (
<ErrorScreen
testId="error-screen"
error={preFlightCheck.error}
cta={() => onExitError?.({ connectorId })}
/>
)
}

return (
<AndroidSafeAreaView testId={testId}>
Expand Down
47 changes: 0 additions & 47 deletions packages/react-native/src/utils/connector/checkConnectorUrl.ts

This file was deleted.

9 changes: 0 additions & 9 deletions packages/react-native/src/utils/connector/handleOAuthUrl.ts

This file was deleted.

Empty file.
2 changes: 0 additions & 2 deletions packages/react-native/src/utils/connector/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/react-native/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './connector'
export * from './error'
Loading
Loading