UNPKG

@tryfinch/react-connect

Version:

The SDK for Finch's authorization flow, Finch Connect

183 lines (178 loc) 6.76 kB
import { useRef, useEffect } from 'react'; const appendBaseParams = (url) => { url.searchParams.append('app_type', 'spa'); url.searchParams.append('sdk_host_url', window.location.origin); url.searchParams.append('mode', 'employer'); url.searchParams.append('sdk_version', 'unified-5.0.1'); }; const constructAuthUrl = ({ sessionId, state, apiConfig, }) => { const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com'; const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI; const authUrl = new URL(`${CONNECT_URL}/authorize`); authUrl.searchParams.append('session', sessionId); if (state) authUrl.searchParams.append('state', state); appendBaseParams(authUrl); return authUrl.href; }; const constructPreviewUrl = ({ clientId, products, apiConfig, }) => { const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com'; const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI; const previewUrl = new URL(`${CONNECT_URL}/authorize`); previewUrl.searchParams.append('preview', 'true'); previewUrl.searchParams.append('client_id', clientId); previewUrl.searchParams.append('products', products.join(' ')); previewUrl.searchParams.append('redirect_uri', 'https://www.tryfinch.com'); appendBaseParams(previewUrl); return previewUrl.href; }; const POST_MESSAGE_NAME = 'finch-auth-message-v2'; const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com'; const FINCH_CONNECT_IFRAME_ID = 'finch-connect-iframe'; const createAndAttachIFrame = ({ src, zIndex = 999, }) => { const existingIframe = document.getElementById(FINCH_CONNECT_IFRAME_ID); if (existingIframe) { existingIframe.remove(); } const iframe = document.createElement('iframe'); iframe.src = src; iframe.frameBorder = '0'; iframe.id = FINCH_CONNECT_IFRAME_ID; iframe.style.position = 'fixed'; iframe.style.zIndex = zIndex.toString(); iframe.style.height = '100%'; iframe.style.width = '100%'; iframe.style.top = '0'; iframe.style.backgroundColor = 'none transparent'; iframe.style.border = 'none'; iframe.allow = 'clipboard-write; clipboard-read'; document.body.prepend(iframe); document.body.style.overflow = 'hidden'; return iframe; }; const removeIFrame = () => { const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID); if (frameToRemove) { frameToRemove.remove(); document.body.style.overflow = 'inherit'; } }; const createMessageHandler = (callbacks, closeFn) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (event) => { var _a, _b, _c, _d; const typedEvent = event; if (!typedEvent.data) return; if (typedEvent.data.name !== POST_MESSAGE_NAME) return; const CONNECT_URL = ((_a = callbacks.apiConfig) === null || _a === void 0 ? void 0 : _a.connectUrl) || BASE_FINCH_CONNECT_URI; if (!typedEvent.origin.startsWith(CONNECT_URL)) return; if (typedEvent.data.kind !== 'error') closeFn(); switch (typedEvent.data.kind) { case 'closed': callbacks.onClose(); break; case 'error': if ((_b = typedEvent.data.error) === null || _b === void 0 ? void 0 : _b.shouldClose) closeFn(); callbacks.onError({ errorMessage: ((_c = typedEvent.data.error) === null || _c === void 0 ? void 0 : _c.message) || 'Unknown error', errorType: (_d = typedEvent.data.error) === null || _d === void 0 ? void 0 : _d.type, }); break; case 'success': callbacks.onSuccess({ code: typedEvent.data.code, state: typedEvent.data.state, idpRedirectUri: typedEvent.data.idpRedirectUri, }); break; default: callbacks.onError({ errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(typedEvent.data)}`, }); } }; }; const createFinchConnectCore = (callbacks) => { const close = () => { removeIFrame(); }; const messageHandler = createMessageHandler(callbacks, close); const open = (args) => { const url = constructAuthUrl({ sessionId: args.sessionId, state: args.state, apiConfig: callbacks.apiConfig, }); createAndAttachIFrame({ src: url, zIndex: args.zIndex, }); }; const openPreview = (args) => { const url = constructPreviewUrl({ clientId: args.clientId, products: args.products, apiConfig: callbacks.apiConfig, }); createAndAttachIFrame({ src: url, }); }; const destroy = () => { close(); window.removeEventListener('message', messageHandler); }; window.addEventListener('message', messageHandler); return { open, openPreview, close, destroy, }; }; let isUseFinchConnectInitialized = false; const useFinchConnect = (initializeArgs) => { const isHookMounted = useRef(false); const coreRef = useRef(null); useEffect(() => { if (!isHookMounted.current) { if (isUseFinchConnectInitialized) { console.error('One useFinchConnect hook has already been registered. Please ensure to only call useFinchConnect once to avoid your event callbacks getting called more than once. You can pass in override options to the open function if you so require.'); } else { isUseFinchConnectInitialized = true; } isHookMounted.current = true; coreRef.current = createFinchConnectCore(initializeArgs); } }, []); useEffect(() => { return () => { if (coreRef.current) { coreRef.current.destroy(); } isUseFinchConnectInitialized = false; isHookMounted.current = false; }; }, []); const open = (launchArgs) => { if (coreRef.current) { coreRef.current.open(launchArgs); } }; const openPreview = (launchArgs) => { if (coreRef.current) { coreRef.current.openPreview(launchArgs); } }; return { open, openPreview, }; }; export { useFinchConnect };