@tryfinch/react-connect 3.15.1 → 4.1.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/.eslintrc.js +12 -0
- package/.github/pull_request_template.md +66 -0
- package/.github/workflows/pr.yml +92 -0
- package/.github/workflows/release.yml +80 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +11 -0
- package/README.md +17 -10
- package/dist/index.d.ts +23 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +55 -66
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +56 -65
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/example/package-lock.json +13 -1
- package/example/src/App.tsx +37 -10
- package/jest.config.js +7 -0
- package/package.json +9 -1
- package/src/index.test.ts +111 -0
- package/src/index.ts +95 -115
package/src/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
|
-
type HasKey<T, K extends PropertyKey> = T extends Record<K, unknown> ? T : never;
|
|
4
|
-
|
|
5
3
|
export type SuccessEvent = {
|
|
6
4
|
code: string;
|
|
7
5
|
state?: string;
|
|
@@ -9,50 +7,36 @@ export type SuccessEvent = {
|
|
|
9
7
|
};
|
|
10
8
|
|
|
11
9
|
type ErrorType = 'validation_error' | 'employer_connection_error';
|
|
12
|
-
|
|
13
10
|
export type ErrorEvent = {
|
|
14
11
|
errorMessage: string;
|
|
15
12
|
errorType?: ErrorType;
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| boolean /** This is the old sandbox flag retained for backwards compatibility */;
|
|
15
|
+
type ApiConfig = {
|
|
16
|
+
connectUrl: string;
|
|
17
|
+
};
|
|
22
18
|
|
|
23
|
-
type
|
|
24
|
-
state: string | null;
|
|
19
|
+
export type ConnectInitializeArgs = {
|
|
25
20
|
onSuccess: (e: SuccessEvent) => void;
|
|
26
21
|
onError: (e: ErrorEvent) => void;
|
|
27
22
|
onClose: () => void;
|
|
28
|
-
|
|
29
|
-
apiConfig?: {
|
|
30
|
-
connectUrl: string;
|
|
31
|
-
redirectUrl: string;
|
|
32
|
-
};
|
|
23
|
+
apiConfig?: ApiConfig;
|
|
33
24
|
};
|
|
34
25
|
|
|
35
|
-
type
|
|
36
|
-
// Use this option if you have a Finch Connect sessionID from the IDP redirect flow
|
|
26
|
+
export type ConnectLaunchArgs = {
|
|
37
27
|
sessionId: string;
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
state?: string;
|
|
29
|
+
zIndex?: number;
|
|
40
30
|
};
|
|
41
31
|
|
|
42
|
-
type
|
|
43
|
-
category: string | null;
|
|
32
|
+
export type ConnectPreviewLaunchArgs = {
|
|
44
33
|
clientId: string;
|
|
45
|
-
manual: boolean;
|
|
46
|
-
payrollProvider: string | null;
|
|
47
34
|
products: string[];
|
|
48
|
-
clientName?: string;
|
|
49
|
-
connectionId?: string;
|
|
50
|
-
sandbox: Sandbox;
|
|
51
35
|
};
|
|
52
36
|
|
|
53
|
-
|
|
37
|
+
type OpenFn = (args: ConnectLaunchArgs) => void;
|
|
54
38
|
|
|
55
|
-
type
|
|
39
|
+
type OpenPreviewFn = (args: ConnectPreviewLaunchArgs) => void;
|
|
56
40
|
|
|
57
41
|
const POST_MESSAGE_NAME = 'finch-auth-message-v2' as const;
|
|
58
42
|
|
|
@@ -78,95 +62,69 @@ interface FinchConnectPostMessage {
|
|
|
78
62
|
}
|
|
79
63
|
|
|
80
64
|
const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
|
|
81
|
-
const DEFAULT_FINCH_REDIRECT_URI = 'https://tryfinch.com';
|
|
82
65
|
|
|
83
66
|
const FINCH_CONNECT_IFRAME_ID = 'finch-connect-iframe';
|
|
84
67
|
|
|
85
|
-
const
|
|
86
|
-
|
|
68
|
+
const appendBaseParams = (url: URL) => {
|
|
69
|
+
url.searchParams.append('app_type', 'spa');
|
|
70
|
+
// The host URL of the SDK. This is used to store the referrer for postMessage purposes
|
|
71
|
+
url.searchParams.append('sdk_host_url', window.location.origin);
|
|
72
|
+
url.searchParams.append('mode', 'employer');
|
|
73
|
+
// replace with actual SDK version by rollup
|
|
74
|
+
url.searchParams.append('sdk_version', 'react-SDK_VERSION');
|
|
75
|
+
};
|
|
87
76
|
|
|
77
|
+
export const constructAuthUrl = ({
|
|
78
|
+
sessionId,
|
|
79
|
+
state,
|
|
80
|
+
apiConfig,
|
|
81
|
+
}: {
|
|
82
|
+
sessionId: string;
|
|
83
|
+
state?: string;
|
|
84
|
+
apiConfig?: ApiConfig;
|
|
85
|
+
}) => {
|
|
88
86
|
const CONNECT_URL = apiConfig?.connectUrl || BASE_FINCH_CONNECT_URI;
|
|
89
|
-
const REDIRECT_URL = apiConfig?.redirectUrl || DEFAULT_FINCH_REDIRECT_URI;
|
|
90
87
|
|
|
91
88
|
const authUrl = new URL(`${CONNECT_URL}/authorize`);
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
const { sessionId, products } = connectOptions;
|
|
95
|
-
authUrl.searchParams.append('session', sessionId);
|
|
96
|
-
if (products) authUrl.searchParams.append('products', products.join(' '));
|
|
97
|
-
} else {
|
|
98
|
-
const {
|
|
99
|
-
clientId,
|
|
100
|
-
payrollProvider,
|
|
101
|
-
category,
|
|
102
|
-
products,
|
|
103
|
-
manual,
|
|
104
|
-
sandbox,
|
|
105
|
-
clientName,
|
|
106
|
-
connectionId,
|
|
107
|
-
} = connectOptions;
|
|
108
|
-
|
|
109
|
-
if (clientId) authUrl.searchParams.append('client_id', clientId);
|
|
110
|
-
if (payrollProvider) authUrl.searchParams.append('payroll_provider', payrollProvider);
|
|
111
|
-
if (category) authUrl.searchParams.append('category', category);
|
|
112
|
-
if (clientName) authUrl.searchParams.append('client_name', clientName);
|
|
113
|
-
if (connectionId) authUrl.searchParams.append('connection_id', connectionId);
|
|
114
|
-
authUrl.searchParams.append('products', (products ?? []).join(' '));
|
|
115
|
-
if (manual) authUrl.searchParams.append('manual', String(manual));
|
|
116
|
-
if (sandbox) authUrl.searchParams.append('sandbox', String(sandbox));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
authUrl.searchParams.append('app_type', 'spa');
|
|
120
|
-
authUrl.searchParams.append('redirect_uri', REDIRECT_URL);
|
|
121
|
-
/** The host URL of the SDK. This is used to store the referrer for postMessage purposes */
|
|
122
|
-
authUrl.searchParams.append('sdk_host_url', window.location.origin);
|
|
123
|
-
authUrl.searchParams.append('mode', 'employer');
|
|
90
|
+
authUrl.searchParams.append('session', sessionId);
|
|
124
91
|
if (state) authUrl.searchParams.append('state', state);
|
|
125
|
-
|
|
126
|
-
authUrl
|
|
92
|
+
|
|
93
|
+
appendBaseParams(authUrl);
|
|
127
94
|
|
|
128
95
|
return authUrl.href;
|
|
129
96
|
};
|
|
130
97
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
98
|
+
export const constructPreviewUrl = ({
|
|
99
|
+
clientId,
|
|
100
|
+
products,
|
|
101
|
+
apiConfig,
|
|
102
|
+
}: {
|
|
103
|
+
clientId: string;
|
|
104
|
+
products: string[];
|
|
105
|
+
apiConfig?: ApiConfig;
|
|
106
|
+
}) => {
|
|
107
|
+
const CONNECT_URL = apiConfig?.connectUrl || BASE_FINCH_CONNECT_URI;
|
|
134
108
|
|
|
135
|
-
const
|
|
136
|
-
onSuccess: noop,
|
|
137
|
-
onError: noop,
|
|
138
|
-
onClose: noop,
|
|
139
|
-
state: null,
|
|
140
|
-
zIndex: 999,
|
|
141
|
-
};
|
|
109
|
+
const previewUrl = new URL(`${CONNECT_URL}/authorize`);
|
|
142
110
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
sandbox: false,
|
|
152
|
-
};
|
|
111
|
+
previewUrl.searchParams.append('preview', 'true');
|
|
112
|
+
previewUrl.searchParams.append('client_id', clientId);
|
|
113
|
+
previewUrl.searchParams.append('products', (products ?? []).join(' '));
|
|
114
|
+
// This will be replaced by a universally allowed redirect URI in the backend
|
|
115
|
+
// We won't ever redirect to anything in preview mode, so we just need a value to pass validation
|
|
116
|
+
previewUrl.searchParams.append('redirect_uri', 'https://www.tryfinch.com');
|
|
117
|
+
|
|
118
|
+
appendBaseParams(previewUrl);
|
|
153
119
|
|
|
154
|
-
|
|
155
|
-
...BASE_DEFAULTS,
|
|
156
|
-
sessionId: '',
|
|
120
|
+
return previewUrl.href;
|
|
157
121
|
};
|
|
158
122
|
|
|
159
123
|
let isUseFinchConnectInitialized = false;
|
|
160
124
|
|
|
161
|
-
export const useFinchConnect = (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if ('sessionId' in options && 'clientId' in options) {
|
|
167
|
-
throw new Error('cannot specify both sessionId and clientId in options for useFinchConnect');
|
|
168
|
-
}
|
|
169
|
-
|
|
125
|
+
export const useFinchConnect = (
|
|
126
|
+
initializeArgs: ConnectInitializeArgs
|
|
127
|
+
): { open: OpenFn; openPreview: OpenPreviewFn } => {
|
|
170
128
|
const isHookMounted = useRef(false);
|
|
171
129
|
|
|
172
130
|
useEffect(() => {
|
|
@@ -183,24 +141,20 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
183
141
|
}
|
|
184
142
|
}, []);
|
|
185
143
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
...combinedOptions,
|
|
194
|
-
...overrides,
|
|
195
|
-
};
|
|
196
|
-
|
|
144
|
+
const createAndAttachIFrame = ({
|
|
145
|
+
src,
|
|
146
|
+
zIndexOverride,
|
|
147
|
+
}: {
|
|
148
|
+
src: string;
|
|
149
|
+
zIndexOverride?: number;
|
|
150
|
+
}) => {
|
|
197
151
|
if (!document.getElementById(FINCH_CONNECT_IFRAME_ID)) {
|
|
198
152
|
const iframe = document.createElement('iframe');
|
|
199
|
-
iframe.src =
|
|
153
|
+
iframe.src = src;
|
|
200
154
|
iframe.frameBorder = '0';
|
|
201
155
|
iframe.id = FINCH_CONNECT_IFRAME_ID;
|
|
202
156
|
iframe.style.position = 'fixed';
|
|
203
|
-
iframe.style.zIndex =
|
|
157
|
+
iframe.style.zIndex = zIndexOverride?.toString() || '999';
|
|
204
158
|
iframe.style.height = '100%';
|
|
205
159
|
iframe.style.width = '100%';
|
|
206
160
|
iframe.style.top = '0';
|
|
@@ -212,6 +166,31 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
212
166
|
}
|
|
213
167
|
};
|
|
214
168
|
|
|
169
|
+
const open: OpenFn = (launchArgs) => {
|
|
170
|
+
const iframeSrc = constructAuthUrl({
|
|
171
|
+
sessionId: launchArgs.sessionId,
|
|
172
|
+
state: launchArgs.state,
|
|
173
|
+
apiConfig: initializeArgs.apiConfig,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
createAndAttachIFrame({
|
|
177
|
+
src: iframeSrc,
|
|
178
|
+
zIndexOverride: launchArgs.zIndex,
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const openPreview: OpenPreviewFn = (launchArgs) => {
|
|
183
|
+
const iframeSrc = constructPreviewUrl({
|
|
184
|
+
clientId: launchArgs.clientId,
|
|
185
|
+
products: launchArgs.products,
|
|
186
|
+
apiConfig: initializeArgs.apiConfig,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
createAndAttachIFrame({
|
|
190
|
+
src: iframeSrc,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
215
194
|
const close = () => {
|
|
216
195
|
const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID);
|
|
217
196
|
if (frameToRemove) {
|
|
@@ -222,7 +201,7 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
222
201
|
|
|
223
202
|
useEffect(() => {
|
|
224
203
|
function handleFinchAuth(event: FinchConnectPostMessage) {
|
|
225
|
-
const CONNECT_URL =
|
|
204
|
+
const CONNECT_URL = initializeArgs.apiConfig?.connectUrl || BASE_FINCH_CONNECT_URI;
|
|
226
205
|
|
|
227
206
|
if (!event.data) return;
|
|
228
207
|
if (event.data.name !== POST_MESSAGE_NAME) return;
|
|
@@ -232,18 +211,18 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
232
211
|
|
|
233
212
|
switch (event.data.kind) {
|
|
234
213
|
case 'closed':
|
|
235
|
-
|
|
214
|
+
initializeArgs.onClose();
|
|
236
215
|
break;
|
|
237
216
|
case 'error':
|
|
238
217
|
if (event.data.error?.shouldClose) close();
|
|
239
218
|
|
|
240
|
-
|
|
219
|
+
initializeArgs.onError({
|
|
241
220
|
errorMessage: event.data.error?.message,
|
|
242
221
|
errorType: event.data.error?.type,
|
|
243
222
|
});
|
|
244
223
|
break;
|
|
245
224
|
case 'success':
|
|
246
|
-
|
|
225
|
+
initializeArgs.onSuccess({
|
|
247
226
|
code: event.data.code,
|
|
248
227
|
state: event.data.state,
|
|
249
228
|
idpRedirectUri: event.data.idpRedirectUri,
|
|
@@ -251,7 +230,7 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
251
230
|
break;
|
|
252
231
|
default: {
|
|
253
232
|
// This case should never happen, if it does it should be reported to us
|
|
254
|
-
|
|
233
|
+
initializeArgs.onError({
|
|
255
234
|
errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(
|
|
256
235
|
event.data
|
|
257
236
|
)}`,
|
|
@@ -265,9 +244,10 @@ export const useFinchConnect = (options: Partial<ConnectOptions>): { open: OpenF
|
|
|
265
244
|
window.removeEventListener('message', handleFinchAuth);
|
|
266
245
|
isUseFinchConnectInitialized = false;
|
|
267
246
|
};
|
|
268
|
-
}, [
|
|
247
|
+
}, [initializeArgs.onClose, initializeArgs.onError, initializeArgs.onSuccess]);
|
|
269
248
|
|
|
270
249
|
return {
|
|
271
250
|
open,
|
|
251
|
+
openPreview,
|
|
272
252
|
};
|
|
273
253
|
};
|