Skip to content

Commit

Permalink
[react-native] Refactor "QuilttConnector" for better visibility (#266)
Browse files Browse the repository at this point in the history
* [react-native] Refactor "QuilttConnector" for better visibility

* Refactor checkConnectorUrl

* Update tests

* Revert 'Linking' module

* Update pnpm/action-setup
  • Loading branch information
zubairaziz authored Jun 18, 2024
1 parent af41481 commit 486ab04
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 313 deletions.
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"
},
"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:'
},
[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

0 comments on commit 486ab04

Please sign in to comment.