@tryfinch/react-connect 4.0.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,35 +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
- type OpenFn = (args: ConnectLaunchArgs) => void;
26
- export declare const constructAuthUrl: ({ sessionId, state, apiConfig, }: {
27
- sessionId: string;
28
- state?: string | undefined;
29
- apiConfig?: ApiConfig | undefined;
30
- }) => string;
31
- export declare const useFinchConnect: (initializeArgs: ConnectInitializeArgs) => {
32
- open: OpenFn;
33
- };
34
- export {};
35
- //# 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,25 +1,176 @@
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';
3
+ const appendBaseParams = (url) => {
4
+ url.searchParams.append('app_type', 'spa');
5
+ url.searchParams.append('sdk_host_url', window.location.origin);
6
+ url.searchParams.append('mode', 'employer');
7
+ url.searchParams.append('sdk_version', 'unified-5.0.0');
8
+ };
6
9
  const constructAuthUrl = ({ sessionId, state, apiConfig, }) => {
10
+ const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
7
11
  const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI;
8
12
  const authUrl = new URL(`${CONNECT_URL}/authorize`);
9
13
  authUrl.searchParams.append('session', sessionId);
10
- authUrl.searchParams.append('app_type', 'spa');
11
- /** The host URL of the SDK. This is used to store the referrer for postMessage purposes */
12
- authUrl.searchParams.append('sdk_host_url', window.location.origin);
13
- authUrl.searchParams.append('mode', 'employer');
14
14
  if (state)
15
15
  authUrl.searchParams.append('state', state);
16
- // replace with actual SDK version by rollup
17
- authUrl.searchParams.append('sdk_version', 'react-4.0.0');
16
+ appendBaseParams(authUrl);
18
17
  return authUrl.href;
19
18
  };
19
+ const constructPreviewUrl = ({ clientId, products, apiConfig, }) => {
20
+ const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
21
+ const CONNECT_URL = (apiConfig === null || apiConfig === void 0 ? void 0 : apiConfig.connectUrl) || BASE_FINCH_CONNECT_URI;
22
+ const previewUrl = new URL(`${CONNECT_URL}/authorize`);
23
+ previewUrl.searchParams.append('preview', 'true');
24
+ previewUrl.searchParams.append('client_id', clientId);
25
+ previewUrl.searchParams.append('products', products.join(' '));
26
+ previewUrl.searchParams.append('redirect_uri', 'https://www.tryfinch.com');
27
+ appendBaseParams(previewUrl);
28
+ return previewUrl.href;
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
+
20
170
  let isUseFinchConnectInitialized = false;
21
171
  const useFinchConnect = (initializeArgs) => {
22
172
  const isHookMounted = useRef(false);
173
+ const coreRef = useRef(null);
23
174
  useEffect(() => {
24
175
  if (!isHookMounted.current) {
25
176
  if (isUseFinchConnectInitialized) {
@@ -29,88 +180,31 @@ const useFinchConnect = (initializeArgs) => {
29
180
  isUseFinchConnectInitialized = true;
30
181
  }
31
182
  isHookMounted.current = true;
183
+ coreRef.current = createFinchConnectCore(initializeArgs);
32
184
  }
33
185
  }, []);
186
+ useEffect(() => {
187
+ return () => {
188
+ if (coreRef.current) {
189
+ coreRef.current.destroy();
190
+ }
191
+ isUseFinchConnectInitialized = false;
192
+ };
193
+ }, []);
34
194
  const open = (launchArgs) => {
35
- var _a;
36
- if (!document.getElementById(FINCH_CONNECT_IFRAME_ID)) {
37
- const iframe = document.createElement('iframe');
38
- iframe.src = constructAuthUrl({
39
- sessionId: launchArgs.sessionId,
40
- state: launchArgs.state,
41
- apiConfig: initializeArgs.apiConfig,
42
- });
43
- iframe.frameBorder = '0';
44
- iframe.id = FINCH_CONNECT_IFRAME_ID;
45
- iframe.style.position = 'fixed';
46
- iframe.style.zIndex = ((_a = launchArgs.zIndex) === null || _a === void 0 ? void 0 : _a.toString()) || '999';
47
- iframe.style.height = '100%';
48
- iframe.style.width = '100%';
49
- iframe.style.top = '0';
50
- iframe.style.backgroundColor = 'none transparent';
51
- iframe.style.border = 'none';
52
- iframe.allow = 'clipboard-write; clipboard-read';
53
- document.body.prepend(iframe);
54
- document.body.style.overflow = 'hidden';
195
+ if (coreRef.current) {
196
+ coreRef.current.open(launchArgs);
55
197
  }
56
198
  };
57
- const close = () => {
58
- var _a;
59
- const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID);
60
- if (frameToRemove) {
61
- (_a = frameToRemove.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(frameToRemove);
62
- document.body.style.overflow = 'inherit';
199
+ const openPreview = (launchArgs) => {
200
+ if (coreRef.current) {
201
+ coreRef.current.openPreview(launchArgs);
63
202
  }
64
203
  };
65
- useEffect(() => {
66
- function handleFinchAuth(event) {
67
- var _a, _b, _c, _d;
68
- const CONNECT_URL = ((_a = initializeArgs.apiConfig) === null || _a === void 0 ? void 0 : _a.connectUrl) || BASE_FINCH_CONNECT_URI;
69
- if (!event.data)
70
- return;
71
- if (event.data.name !== POST_MESSAGE_NAME)
72
- return;
73
- if (!event.origin.startsWith(CONNECT_URL))
74
- return;
75
- if (event.data.kind !== 'error')
76
- close();
77
- switch (event.data.kind) {
78
- case 'closed':
79
- initializeArgs.onClose();
80
- break;
81
- case 'error':
82
- if ((_b = event.data.error) === null || _b === void 0 ? void 0 : _b.shouldClose)
83
- close();
84
- initializeArgs.onError({
85
- errorMessage: (_c = event.data.error) === null || _c === void 0 ? void 0 : _c.message,
86
- errorType: (_d = event.data.error) === null || _d === void 0 ? void 0 : _d.type,
87
- });
88
- break;
89
- case 'success':
90
- initializeArgs.onSuccess({
91
- code: event.data.code,
92
- state: event.data.state,
93
- idpRedirectUri: event.data.idpRedirectUri,
94
- });
95
- break;
96
- default: {
97
- // This case should never happen, if it does it should be reported to us
98
- initializeArgs.onError({
99
- errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(event.data)}`,
100
- });
101
- }
102
- }
103
- }
104
- window.addEventListener('message', handleFinchAuth);
105
- return () => {
106
- window.removeEventListener('message', handleFinchAuth);
107
- isUseFinchConnectInitialized = false;
108
- };
109
- }, [initializeArgs.onClose, initializeArgs.onError, initializeArgs.onSuccess]);
110
204
  return {
111
205
  open,
206
+ openPreview,
112
207
  };
113
208
  };
114
209
 
115
- export { constructAuthUrl, useFinchConnect };
116
- //# sourceMappingURL=index.es.js.map
210
+ export { FinchConnect, useFinchConnect };