@tryfinch/react-connect 4.1.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,41 @@
1
- # @tryfinch/react-connect
1
+ # Finch Connect SDK
2
2
 
3
3
  [![NPM](https://img.shields.io/npm/v/@tryfinch/react-connect)](https://www.npmjs.com/package/@tryfinch/react-connect)
4
4
 
5
- ## Install
5
+ The SDK for [Finch's](https://developer.tryfinch.com/how-finch-works/quickstart) authorization flow, Finch Connect
6
+
7
+ ## Installation
8
+
9
+ ### React
10
+
11
+ Available on [npm](https://www.npmjs.com/package/@tryfinch/react-connect):
6
12
 
7
13
  ```bash
8
- npm install --save @tryfinch/react-connect
14
+ npm install @tryfinch/react-connect
15
+ ```
16
+
17
+ ### JavaScript
18
+
19
+ Available via CDN:
20
+ > Note: Since Finch Connect is an iFrame that requires interactivity, the HTML page that is loading the SDK **must be served from a server**. If the page is hosted statically Finch Connect will not work properly.
21
+
22
+ ```html
23
+ <script src="https://prod-cdn.tryfinch.com/latest/connect.js"></script>
24
+ ```
25
+
26
+ Pin to a specific version (see [npm](https://www.npmjs.com/package/@tryfinch/react-connect) for available versions):
27
+
28
+ ```html
29
+ <script src="https://prod-cdn.tryfinch.com/vX.Y.Z/connect.js"></script>
9
30
  ```
10
31
 
11
32
  ## Usage
12
33
 
13
- See the example app at `example/src` for a functional example
34
+ See the documentation on setting up Finch Connect [here](https://developer.tryfinch.com/implementation-guide/Connect/Create-Account)
35
+
36
+ Example apps are available in the `example` directory of this repo. See their READMEs for how to run them.
37
+
38
+ ### React
14
39
 
15
40
  ```jsx
16
41
  import React, { useState } from 'react';
@@ -32,7 +57,8 @@ const App = () => {
32
57
  * - 'validation_error': Finch Connect failed to open due to validation error
33
58
  * - 'employer_connection_error': The errors employers see within the Finch Connect flow
34
59
  */
35
- const onError = ({ errorMessage, errorType }) => console.error(errorMessage, errorType);
60
+ const onError = ({ errorMessage, errorType }) =>
61
+ console.error(errorMessage, errorType);
36
62
  const onClose = () => console.log('User exited Finch Connect');
37
63
 
38
64
  // Initialize the FinchConnect hook
@@ -58,3 +84,59 @@ const App = () => {
58
84
  );
59
85
  };
60
86
  ```
87
+
88
+ ### JavaScript
89
+
90
+ ```html
91
+ <html>
92
+ <head>
93
+ <script src="https://prod-cdn.tryfinch.com/latest/connect.js"></script>
94
+ </head>
95
+ <body>
96
+ <button id="connect-button">Open Finch Connect</button>
97
+
98
+ <script>
99
+ // Define callbacks
100
+
101
+ /**
102
+ * @param {string} code - The authorization code to exchange for an access token
103
+ * @param {string?} state - The state value that was provided when launching Connect
104
+ */
105
+ const onSuccess = ({ code, state }) => {
106
+ // Exchange code for access token via your server
107
+ };
108
+ /**
109
+ * @param {string} errorMessage - The error message
110
+ * @param {'validation_error' | 'employer_error'} errorType - The type of error
111
+ * - 'validation_error': Finch Connect failed to open due to validation error
112
+ * - 'employer_connection_error': The errors employers see within the Finch Connect flow
113
+ */
114
+ const onError = ({ errorMessage }) => {
115
+ console.error(errorMessage);
116
+ };
117
+ const onClose = () => {
118
+ console.log('Connect closed');
119
+ };
120
+
121
+ const connect = FinchConnect.initialize({
122
+ onSuccess,
123
+ onError,
124
+ onClose,
125
+ });
126
+
127
+ // Generate a session ID using the /connect/sessions endpoint on the Finch API
128
+ // See the docs here https://developer.tryfinch.com/api-reference/connect/new-session#create-a-new-connect-session
129
+ const sessionId = '';
130
+
131
+ const button = document.getElementById('connect-button');
132
+ button.addEventListener('click', () => {
133
+ connect.open({ sessionId });
134
+ });
135
+ </script>
136
+ </body>
137
+ </html>
138
+ ```
139
+
140
+ ## Contributing
141
+
142
+ Pull requests in this repo are not routinely reviewed. Do not submit pull requests if you are having issues with Finch Connect. Instead please reach out to our support team at developers@tryfinch.com.
package/dist/core.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { ConnectInitializeArgs, FinchConnectInterface } from './types';
2
+ export declare const POST_MESSAGE_NAME: "finch-auth-message-v2";
3
+ export declare const BASE_FINCH_CONNECT_URI = "https://connect.tryfinch.com";
4
+ export declare const FINCH_CONNECT_IFRAME_ID = "finch-connect-iframe";
5
+ export declare const createAndAttachIFrame: ({ src, zIndex, }: {
6
+ src: string;
7
+ zIndex?: number;
8
+ }) => HTMLIFrameElement;
9
+ export declare const removeIFrame: () => void;
10
+ export declare const createMessageHandler: (callbacks: ConnectInitializeArgs, closeFn: () => void) => (event: MessageEvent<any>) => void;
11
+ export declare const createFinchConnectCore: (callbacks: ConnectInitializeArgs) => FinchConnectInterface;
@@ -0,0 +1,2 @@
1
+ export * from './index';
2
+ export { useFinchConnect } from './react';
package/dist/index.d.ts CHANGED
@@ -1,46 +1,12 @@
1
- export type SuccessEvent = {
2
- code: string;
3
- state?: string;
4
- idpRedirectUri?: string;
5
- };
6
- type ErrorType = 'validation_error' | 'employer_connection_error';
7
- export type ErrorEvent = {
8
- errorMessage: string;
9
- errorType?: ErrorType;
10
- };
11
- type ApiConfig = {
12
- connectUrl: string;
13
- };
14
- export type ConnectInitializeArgs = {
15
- onSuccess: (e: SuccessEvent) => void;
16
- onError: (e: ErrorEvent) => void;
17
- onClose: () => void;
18
- apiConfig?: ApiConfig;
19
- };
20
- export type ConnectLaunchArgs = {
21
- sessionId: string;
22
- state?: string;
23
- zIndex?: number;
24
- };
25
- export type ConnectPreviewLaunchArgs = {
26
- clientId: string;
27
- products: string[];
28
- };
29
- type OpenFn = (args: ConnectLaunchArgs) => void;
30
- type OpenPreviewFn = (args: ConnectPreviewLaunchArgs) => void;
31
- export declare const constructAuthUrl: ({ sessionId, state, apiConfig, }: {
32
- sessionId: string;
33
- state?: string | undefined;
34
- apiConfig?: ApiConfig | undefined;
35
- }) => string;
36
- export declare const constructPreviewUrl: ({ clientId, products, apiConfig, }: {
37
- clientId: string;
38
- products: string[];
39
- apiConfig?: ApiConfig | undefined;
40
- }) => string;
41
- export declare const useFinchConnect: (initializeArgs: ConnectInitializeArgs) => {
42
- open: OpenFn;
43
- openPreview: OpenPreviewFn;
44
- };
45
- export {};
46
- //# sourceMappingURL=index.d.ts.map
1
+ import { ConnectInitializeArgs, ConnectLaunchArgs, ConnectPreviewLaunchArgs, FinchConnectInterface } from './types';
2
+ export * from './types';
3
+ export default class FinchConnect implements FinchConnectInterface {
4
+ private core;
5
+ private constructor();
6
+ static initialize(args: ConnectInitializeArgs): FinchConnect;
7
+ open: (args: ConnectLaunchArgs) => void;
8
+ openPreview: (args: ConnectPreviewLaunchArgs) => void;
9
+ close: () => void;
10
+ destroy: () => void;
11
+ }
12
+ export { FinchConnect };
package/dist/index.es.js CHANGED
@@ -1,17 +1,13 @@
1
1
  import { useRef, useEffect } from 'react';
2
2
 
3
- const POST_MESSAGE_NAME = 'finch-auth-message-v2';
4
- const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
5
- const FINCH_CONNECT_IFRAME_ID = 'finch-connect-iframe';
6
3
  const appendBaseParams = (url) => {
7
4
  url.searchParams.append('app_type', 'spa');
8
- // The host URL of the SDK. This is used to store the referrer for postMessage purposes
9
5
  url.searchParams.append('sdk_host_url', window.location.origin);
10
6
  url.searchParams.append('mode', 'employer');
11
- // replace with actual SDK version by rollup
12
- url.searchParams.append('sdk_version', 'react-4.1.0');
7
+ url.searchParams.append('sdk_version', 'unified-5.0.1');
13
8
  };
14
9
  const constructAuthUrl = ({ sessionId, state, apiConfig, }) => {
10
+ const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
15
11
  const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI;
16
12
  const authUrl = new URL(`${CONNECT_URL}/authorize`);
17
13
  authUrl.searchParams.append('session', sessionId);
@@ -21,20 +17,160 @@ const constructAuthUrl = ({ sessionId, state, apiConfig, }) => {
21
17
  return authUrl.href;
22
18
  };
23
19
  const constructPreviewUrl = ({ clientId, products, apiConfig, }) => {
20
+ const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
24
21
  const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI;
25
22
  const previewUrl = new URL(`${CONNECT_URL}/authorize`);
26
23
  previewUrl.searchParams.append('preview', 'true');
27
24
  previewUrl.searchParams.append('client_id', clientId);
28
- previewUrl.searchParams.append('products', (products !== null && products !== void 0 ? products : []).join(' '));
29
- // This will be replaced by a universally allowed redirect URI in the backend
30
- // We won't ever redirect to anything in preview mode, so we just need a value to pass validation
25
+ previewUrl.searchParams.append('products', products.join(' '));
31
26
  previewUrl.searchParams.append('redirect_uri', 'https://www.tryfinch.com');
32
27
  appendBaseParams(previewUrl);
33
28
  return previewUrl.href;
34
29
  };
30
+
31
+ const POST_MESSAGE_NAME = 'finch-auth-message-v2';
32
+ const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
33
+ const FINCH_CONNECT_IFRAME_ID = 'finch-connect-iframe';
34
+ const createAndAttachIFrame = ({ src, zIndex = 999, }) => {
35
+ const existingIframe = document.getElementById(FINCH_CONNECT_IFRAME_ID);
36
+ if (existingIframe) {
37
+ existingIframe.remove();
38
+ }
39
+ const iframe = document.createElement('iframe');
40
+ iframe.src = src;
41
+ iframe.frameBorder = '0';
42
+ iframe.id = FINCH_CONNECT_IFRAME_ID;
43
+ iframe.style.position = 'fixed';
44
+ iframe.style.zIndex = zIndex.toString();
45
+ iframe.style.height = '100%';
46
+ iframe.style.width = '100%';
47
+ iframe.style.top = '0';
48
+ iframe.style.backgroundColor = 'none transparent';
49
+ iframe.style.border = 'none';
50
+ iframe.allow = 'clipboard-write; clipboard-read';
51
+ document.body.prepend(iframe);
52
+ document.body.style.overflow = 'hidden';
53
+ return iframe;
54
+ };
55
+ const removeIFrame = () => {
56
+ const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID);
57
+ if (frameToRemove) {
58
+ frameToRemove.remove();
59
+ document.body.style.overflow = 'inherit';
60
+ }
61
+ };
62
+ const createMessageHandler = (callbacks, closeFn) => {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ return (event) => {
65
+ var _a, _b, _c, _d;
66
+ const typedEvent = event;
67
+ if (!typedEvent.data)
68
+ return;
69
+ if (typedEvent.data.name !== POST_MESSAGE_NAME)
70
+ return;
71
+ const CONNECT_URL = ((_a = callbacks.apiConfig) === null || _a === void 0 ? void 0 : _a.connectUrl) || BASE_FINCH_CONNECT_URI;
72
+ if (!typedEvent.origin.startsWith(CONNECT_URL))
73
+ return;
74
+ if (typedEvent.data.kind !== 'error')
75
+ closeFn();
76
+ switch (typedEvent.data.kind) {
77
+ case 'closed':
78
+ callbacks.onClose();
79
+ break;
80
+ case 'error':
81
+ if ((_b = typedEvent.data.error) === null || _b === void 0 ? void 0 : _b.shouldClose)
82
+ closeFn();
83
+ callbacks.onError({
84
+ errorMessage: ((_c = typedEvent.data.error) === null || _c === void 0 ? void 0 : _c.message) || 'Unknown error',
85
+ errorType: (_d = typedEvent.data.error) === null || _d === void 0 ? void 0 : _d.type,
86
+ });
87
+ break;
88
+ case 'success':
89
+ callbacks.onSuccess({
90
+ code: typedEvent.data.code,
91
+ state: typedEvent.data.state,
92
+ idpRedirectUri: typedEvent.data.idpRedirectUri,
93
+ });
94
+ break;
95
+ default:
96
+ callbacks.onError({
97
+ errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(typedEvent.data)}`,
98
+ });
99
+ }
100
+ };
101
+ };
102
+ const createFinchConnectCore = (callbacks) => {
103
+ const close = () => {
104
+ removeIFrame();
105
+ };
106
+ const messageHandler = createMessageHandler(callbacks, close);
107
+ const open = (args) => {
108
+ const url = constructAuthUrl({
109
+ sessionId: args.sessionId,
110
+ state: args.state,
111
+ apiConfig: callbacks.apiConfig,
112
+ });
113
+ createAndAttachIFrame({
114
+ src: url,
115
+ zIndex: args.zIndex,
116
+ });
117
+ };
118
+ const openPreview = (args) => {
119
+ const url = constructPreviewUrl({
120
+ clientId: args.clientId,
121
+ products: args.products,
122
+ apiConfig: callbacks.apiConfig,
123
+ });
124
+ createAndAttachIFrame({
125
+ src: url,
126
+ });
127
+ };
128
+ const destroy = () => {
129
+ close();
130
+ window.removeEventListener('message', messageHandler);
131
+ };
132
+ window.addEventListener('message', messageHandler);
133
+ return {
134
+ open,
135
+ openPreview,
136
+ close,
137
+ destroy,
138
+ };
139
+ };
140
+
141
+ let isInitialized = false;
142
+ class FinchConnect {
143
+ constructor(args) {
144
+ this.open = (args) => {
145
+ this.core.open(args);
146
+ };
147
+ this.openPreview = (args) => {
148
+ this.core.openPreview(args);
149
+ };
150
+ this.close = () => {
151
+ this.core.close();
152
+ };
153
+ this.destroy = () => {
154
+ this.core.destroy();
155
+ isInitialized = false;
156
+ };
157
+ if (isInitialized) {
158
+ 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.');
159
+ }
160
+ else {
161
+ isInitialized = true;
162
+ }
163
+ this.core = createFinchConnectCore(args);
164
+ }
165
+ static initialize(args) {
166
+ return new FinchConnect(args);
167
+ }
168
+ }
169
+
35
170
  let isUseFinchConnectInitialized = false;
36
171
  const useFinchConnect = (initializeArgs) => {
37
172
  const isHookMounted = useRef(false);
173
+ const coreRef = useRef(null);
38
174
  useEffect(() => {
39
175
  if (!isHookMounted.current) {
40
176
  if (isUseFinchConnectInitialized) {
@@ -44,105 +180,32 @@ const useFinchConnect = (initializeArgs) => {
44
180
  isUseFinchConnectInitialized = true;
45
181
  }
46
182
  isHookMounted.current = true;
183
+ coreRef.current = createFinchConnectCore(initializeArgs);
47
184
  }
48
185
  }, []);
49
- const createAndAttachIFrame = ({ src, zIndexOverride, }) => {
50
- if (!document.getElementById(FINCH_CONNECT_IFRAME_ID)) {
51
- const iframe = document.createElement('iframe');
52
- iframe.src = src;
53
- iframe.frameBorder = '0';
54
- iframe.id = FINCH_CONNECT_IFRAME_ID;
55
- iframe.style.position = 'fixed';
56
- iframe.style.zIndex = (zIndexOverride === null || zIndexOverride === void 0 ? void 0 : zIndexOverride.toString()) || '999';
57
- iframe.style.height = '100%';
58
- iframe.style.width = '100%';
59
- iframe.style.top = '0';
60
- iframe.style.backgroundColor = 'none transparent';
61
- iframe.style.border = 'none';
62
- iframe.allow = 'clipboard-write; clipboard-read';
63
- document.body.prepend(iframe);
64
- document.body.style.overflow = 'hidden';
65
- }
66
- };
186
+ useEffect(() => {
187
+ return () => {
188
+ if (coreRef.current) {
189
+ coreRef.current.destroy();
190
+ }
191
+ isUseFinchConnectInitialized = false;
192
+ isHookMounted.current = false;
193
+ };
194
+ }, []);
67
195
  const open = (launchArgs) => {
68
- const iframeSrc = constructAuthUrl({
69
- sessionId: launchArgs.sessionId,
70
- state: launchArgs.state,
71
- apiConfig: initializeArgs.apiConfig,
72
- });
73
- createAndAttachIFrame({
74
- src: iframeSrc,
75
- zIndexOverride: launchArgs.zIndex,
76
- });
196
+ if (coreRef.current) {
197
+ coreRef.current.open(launchArgs);
198
+ }
77
199
  };
78
200
  const openPreview = (launchArgs) => {
79
- const iframeSrc = constructPreviewUrl({
80
- clientId: launchArgs.clientId,
81
- products: launchArgs.products,
82
- apiConfig: initializeArgs.apiConfig,
83
- });
84
- createAndAttachIFrame({
85
- src: iframeSrc,
86
- });
87
- };
88
- const close = () => {
89
- var _a;
90
- const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID);
91
- if (frameToRemove) {
92
- (_a = frameToRemove.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(frameToRemove);
93
- document.body.style.overflow = 'inherit';
201
+ if (coreRef.current) {
202
+ coreRef.current.openPreview(launchArgs);
94
203
  }
95
204
  };
96
- useEffect(() => {
97
- function handleFinchAuth(event) {
98
- var _a, _b, _c, _d;
99
- const CONNECT_URL = ((_a = initializeArgs.apiConfig) === null || _a === void 0 ? void 0 : _a.connectUrl) || BASE_FINCH_CONNECT_URI;
100
- if (!event.data)
101
- return;
102
- if (event.data.name !== POST_MESSAGE_NAME)
103
- return;
104
- if (!event.origin.startsWith(CONNECT_URL))
105
- return;
106
- if (event.data.kind !== 'error')
107
- close();
108
- switch (event.data.kind) {
109
- case 'closed':
110
- initializeArgs.onClose();
111
- break;
112
- case 'error':
113
- if ((_b = event.data.error) === null || _b === void 0 ? void 0 : _b.shouldClose)
114
- close();
115
- initializeArgs.onError({
116
- errorMessage: (_c = event.data.error) === null || _c === void 0 ? void 0 : _c.message,
117
- errorType: (_d = event.data.error) === null || _d === void 0 ? void 0 : _d.type,
118
- });
119
- break;
120
- case 'success':
121
- initializeArgs.onSuccess({
122
- code: event.data.code,
123
- state: event.data.state,
124
- idpRedirectUri: event.data.idpRedirectUri,
125
- });
126
- break;
127
- default: {
128
- // This case should never happen, if it does it should be reported to us
129
- initializeArgs.onError({
130
- errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(event.data)}`,
131
- });
132
- }
133
- }
134
- }
135
- window.addEventListener('message', handleFinchAuth);
136
- return () => {
137
- window.removeEventListener('message', handleFinchAuth);
138
- isUseFinchConnectInitialized = false;
139
- };
140
- }, [initializeArgs.onClose, initializeArgs.onError, initializeArgs.onSuccess]);
141
205
  return {
142
206
  open,
143
207
  openPreview,
144
208
  };
145
209
  };
146
210
 
147
- export { constructAuthUrl, constructPreviewUrl, useFinchConnect };
148
- //# sourceMappingURL=index.es.js.map
211
+ export { FinchConnect, useFinchConnect };