@tryfinch/react-connect
Version:
The SDK for Finch's authorization flow, Finch Connect
177 lines (171 loc) • 7.21 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.FinchConnect = factory());
})(this, (function () { 'use strict';
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 isInitialized = false;
class FinchConnect {
constructor(args) {
this.open = (args) => {
this.core.open(args);
};
this.openPreview = (args) => {
this.core.openPreview(args);
};
this.close = () => {
this.core.close();
};
this.destroy = () => {
this.core.destroy();
isInitialized = false;
};
if (isInitialized) {
console.error('FinchConnect.initialize has already been called. Please ensure to only call FinchConnect.initialize once to avoid your event callbacks being triggered multiple times.');
}
else {
isInitialized = true;
}
this.core = createFinchConnectCore(args);
}
static initialize(args) {
return new FinchConnect(args);
}
}
return FinchConnect;
}));