@tryfinch/react-connect 4.1.0 → 5.0.0

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,30 @@
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://www.tryfinch.com/) authorization flow, Finch Connect
6
+
7
+ ## Installation
8
+
9
+ ### React
10
+
11
+ Available on npm at `@tryfinch/react-connect`:
6
12
 
7
13
  ```bash
8
- npm install --save @tryfinch/react-connect
14
+ npm install @tryfinch/connect
15
+ ```
16
+
17
+ ### JavaScript
18
+
19
+ Available via CDN:
20
+
21
+ ```html
22
+ <script src="https://prod-cdn.tryfinch.com/latest/connect.js"></script>
9
23
  ```
10
24
 
11
25
  ## Usage
12
26
 
13
- See the example app at `example/src` for a functional example
27
+ ### React
14
28
 
15
29
  ```jsx
16
30
  import React, { useState } from 'react';
@@ -32,7 +46,8 @@ const App = () => {
32
46
  * - 'validation_error': Finch Connect failed to open due to validation error
33
47
  * - 'employer_connection_error': The errors employers see within the Finch Connect flow
34
48
  */
35
- const onError = ({ errorMessage, errorType }) => console.error(errorMessage, errorType);
49
+ const onError = ({ errorMessage, errorType }) =>
50
+ console.error(errorMessage, errorType);
36
51
  const onClose = () => console.log('User exited Finch Connect');
37
52
 
38
53
  // Initialize the FinchConnect hook
@@ -58,3 +73,55 @@ const App = () => {
58
73
  );
59
74
  };
60
75
  ```
76
+
77
+ ### JavaScript
78
+
79
+ ```html
80
+ <html>
81
+ <head>
82
+ <script src="https://prod-cdn.tryfinch.com/latest/connect.js"></script>
83
+ </head>
84
+ <body>
85
+ <button id="connect-button">Open Finch Connect</button>
86
+
87
+ <script>
88
+ // Define callbacks
89
+
90
+ /**
91
+ * @param {string} code - The authorization code to exchange for an access token
92
+ * @param {string?} state - The state value that was provided when launching Connect
93
+ */
94
+ const onSuccess = ({ code, state }) => {
95
+ // Exchange code for access token via your server
96
+ };
97
+ /**
98
+ * @param {string} errorMessage - The error message
99
+ * @param {'validation_error' | 'employer_error'} errorType - The type of error
100
+ * - 'validation_error': Finch Connect failed to open due to validation error
101
+ * - 'employer_connection_error': The errors employers see within the Finch Connect flow
102
+ */
103
+ const onError = ({ errorMessage }) => {
104
+ console.error(errorMessage);
105
+ };
106
+ const onClose = () => {
107
+ console.log('Connect closed');
108
+ };
109
+
110
+ const connect = FinchConnect.initialize({
111
+ onSuccess,
112
+ onError,
113
+ onClose,
114
+ });
115
+
116
+ // Generate a session ID using the /connect/sessions endpoint on the Finch API
117
+ // See the docs here https://developer.tryfinch.com/api-reference/connect/new-session#create-a-new-connect-session
118
+ const sessionId = '';
119
+
120
+ const button = document.getElementById('connect-button');
121
+ button.addEventListener('click', () => {
122
+ connect.open({ sessionId });
123
+ });
124
+ </script>
125
+ </body>
126
+ </html>
127
+ ```
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.0');
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,31 @@ 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
+ };
193
+ }, []);
67
194
  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
- });
195
+ if (coreRef.current) {
196
+ coreRef.current.open(launchArgs);
197
+ }
77
198
  };
78
199
  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';
200
+ if (coreRef.current) {
201
+ coreRef.current.openPreview(launchArgs);
94
202
  }
95
203
  };
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
204
  return {
142
205
  open,
143
206
  openPreview,
144
207
  };
145
208
  };
146
209
 
147
- export { constructAuthUrl, constructPreviewUrl, useFinchConnect };
148
- //# sourceMappingURL=index.es.js.map
210
+ export { FinchConnect, useFinchConnect };