Skip to content

Commit

Permalink
feat: adding recaptcha
Browse files Browse the repository at this point in the history
- inject recaptcha within iframe
- use postMessage to sync state between iframe and parent
  • Loading branch information
bc-micah authored and kris-liu-smile committed Jul 22, 2022
1 parent e61dbae commit 0b75e61
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 5 deletions.
15 changes: 10 additions & 5 deletions apps/storefront/src/components/ThemeFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {
Component,
createRef,
ReactNode,
RefObject,
createContext,
createRef,
} from 'react'
import {
createPortal,
Expand Down Expand Up @@ -31,6 +32,8 @@ export function IFrameSetContent(el: HTMLIFrameElement | null, content: string,
}
}

export const ThemeFrameContext = createContext<null|Document>(null)

interface ThemeFrameProps {
children: ReactNode // children to be rendered within iframe
className?: string // className to assign the iframe
Expand Down Expand Up @@ -138,10 +141,12 @@ export class ThemeFrame extends Component<ThemeFrameProps, ThemeFrameState> {
}

return createPortal(
<CacheProvider value={this.state.emotionCache}>
<CssBaseline />
{this.props.children}
</CacheProvider>,
<ThemeFrameContext.Provider value={doc}>
<CacheProvider value={this.state.emotionCache}>
<CssBaseline />
{this.props.children}
</CacheProvider>
</ThemeFrameContext.Provider>,
doc.body,
)
}
Expand Down
106 changes: 106 additions & 0 deletions apps/storefront/src/components/form/Captcha.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
Component,
ContextType,
} from 'react'
import {
ThemeFrameContext,
} from '@/components/ThemeFrame'

const CAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'
const PREFIX = `${Date.now()}|`
const PARENT_ORIGIN = window.location.origin

export interface CaptchaProps {
siteKey: string
size?: 'compact'|'normal',
theme?: 'dark' | 'light',
onSuccess?: () => void
onError?: () => void
onExpired?: () => void
}

export class Captcha extends Component<CaptchaProps> {
static contextType = ThemeFrameContext

declare context: ContextType<typeof ThemeFrameContext>

_initialized: boolean = false

componentDidMount() {
if (!this._initialized) {
this.initializeFrame()
}
}

componentWillUnmount() {
window.removeEventListener('message', this.onMessage)
}

onMessage = (event: MessageEvent) => {
if (event?.data?.startsWith(`${PREFIX}`)) {
const message = event.data.slice(PREFIX.length)
const data = JSON.parse(message)
switch (data.type) {
case 'captcha-success':
this.props.onSuccess?.()
break

case 'captcha-error':
this.props.onError?.()
break

case 'captcha-expired':
this.props.onExpired?.()
break

default:
break
}
}
}

initializeFrame() {
const doc = this.context
if (doc === null || this._initialized) {
return
}

const captchaScript = doc.createElement('script')
captchaScript.src = CAPTCHA_URL
doc.head.appendChild(captchaScript)

const handlerScript = doc.createElement('script')
handlerScript.innerHTML = `
window.successCallback = function (token) {
window.parent.postMessage('${PREFIX}' + JSON.stringify({type: "captcha-success", payload: token}), '${PARENT_ORIGIN}')
}
window.expiredCallback = function () {
window.parent.postMessage('${PREFIX}' + JSON.stringify({type: "captcha-expired", payload: null}), '${PARENT_ORIGIN}')
}
window.errorCallback = function () {
window.parent.postMessage('${PREFIX}' + JSON.stringify({type: "captcha-expired", payload: null}), '${PARENT_ORIGIN}')
}
`
doc.head.appendChild(handlerScript)

window.addEventListener('message', this.onMessage, false)

this._initialized = true
}

render() {
this.initializeFrame()

return (
<div
className="g-recaptcha"
data-sitekey={this.props.siteKey}
data-theme={this.props.theme}
data-size={this.props.size}
data-callback="successCallback"
data-expired-callback="expiredCallback"
data-error-callback="errorCallback"
/>
)
}
}
3 changes: 3 additions & 0 deletions apps/storefront/src/components/form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export {
export {
B3FileUpload,
} from './B3FileUpload'
export {
Captcha,
} from './Captcha'
18 changes: 18 additions & 0 deletions apps/storefront/src/pages/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,26 @@ import {
Grid,
TextField,
} from '@mui/material'
import {
useState,
} from 'react'
import {
Captcha,
} from '@/components/form'

const Variant = 'filled'
const TEST_SITE_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'

export function Form() {
const [state, setState] = useState<'success'|'error'|'expired'>('error')

return (
<form
onSubmit={(event) => {
event.preventDefault()
if (state === 'success') {
// proceed
}
}}
>
<Grid
Expand Down Expand Up @@ -156,7 +168,13 @@ export function Form() {
justifyContent: 'flex-end',
}}
>
<Captcha
size="normal"
siteKey={TEST_SITE_KEY}
onSuccess={() => setState('success')}
/>
<Button
disabled={state !== 'success'}
type="submit"
variant="contained"
sx={{
Expand Down

0 comments on commit 0b75e61

Please sign in to comment.