@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.
@@ -1,35 +0,0 @@
1
- {
2
- "name": "react-finch-connect-example",
3
- "homepage": "https://.github.io/react-finch-connect",
4
- "version": "0.0.0",
5
- "license": "MIT",
6
- "private": true,
7
- "dependencies": {
8
- "@tryfinch/react-connect": "file:..",
9
- "@types/react": "^17.0.37",
10
- "prop-types": "^15.6.2",
11
- "react": "file:../node_modules/react",
12
- "react-dom": "^16.9.0"
13
- },
14
- "scripts": {
15
- "start": "react-scripts start",
16
- "build": "react-scripts build",
17
- "test": "react-scripts test --env=jsdom",
18
- "eject": "react-scripts eject"
19
- },
20
- "eslintConfig": {
21
- "extends": [
22
- "react-app",
23
- "react-app/jest"
24
- ]
25
- },
26
- "browserslist": [
27
- ">0.2%",
28
- "not dead",
29
- "not ie <= 11",
30
- "not op_mini all"
31
- ],
32
- "devDependencies": {
33
- "react-scripts": "^5.0.1"
34
- }
35
- }
@@ -1,20 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
- <meta name="theme-color" content="#000000">
7
-
8
- <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
9
-
10
- <title>react-finch-connect</title>
11
- </head>
12
-
13
- <body>
14
- <noscript>
15
- You need to enable JavaScript to run this app.
16
- </noscript>
17
-
18
- <div id="root"></div>
19
- </body>
20
- </html>
@@ -1,8 +0,0 @@
1
- {
2
- "short_name": "react-finch-connect",
3
- "name": "react-finch-connect",
4
- "start_url": "./index.html",
5
- "display": "standalone",
6
- "theme_color": "#000000",
7
- "background_color": "#ffffff"
8
- }
@@ -1,49 +0,0 @@
1
- .container {
2
- text-align: center;
3
- background-color: #282c34;
4
- min-height: 100vh;
5
- display: flex;
6
- flex-direction: column;
7
- align-items: center;
8
- justify-content: center;
9
- font-size: calc(7px + 2vmin);
10
- color: white;
11
- }
12
-
13
- a {
14
- color: white;
15
- }
16
-
17
- .actions {
18
- background-color: #4f4f4f;
19
- padding: 1em;
20
- border-radius: 10px;
21
- margin: 1em 0;
22
- }
23
-
24
- .cta {
25
- font-size: calc(7px + 2vmin);
26
- padding: .5em;
27
- }
28
-
29
- .row {
30
- margin: 2em 0;
31
- }
32
-
33
- .top-label {
34
- display: block;
35
- margin-bottom: .2em;
36
- }
37
-
38
- input[type=checkbox] {
39
- width: 2em;
40
- height: 2em;
41
- }
42
-
43
- .results {
44
- background-color: #4f4f4f;
45
- padding: 1em;
46
- border-radius: 10px;
47
- text-align: left;
48
- margin: 1em 0;
49
- }
@@ -1,80 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { useFinchConnect, SuccessEvent, ErrorEvent } from '@tryfinch/react-connect';
3
-
4
- import Result, { ResultContainer } from './Result';
5
-
6
- import './App.css';
7
-
8
- const App = () => {
9
- const [sessionId, setSessionId] = useState<string>('');
10
- const [sendState, setSendState] = useState<boolean>(false);
11
- const [result, setResult] = useState<ResultContainer>();
12
-
13
- // Define callbacks
14
- const onSuccess = (value: SuccessEvent) => setResult({ kind: 'success', value });
15
- const onError = (value: ErrorEvent) => setResult({ kind: 'error', value });
16
- const onClose = () => setResult({ kind: 'closed' });
17
-
18
- // Initialize the FinchConnect hook
19
- const { open } = useFinchConnect({
20
- onSuccess,
21
- onError,
22
- onClose,
23
- });
24
-
25
- // Call the open method when submitting the form
26
- const submissionHandler: React.FormEventHandler<HTMLFormElement> = (e) => {
27
- e.preventDefault();
28
- open({
29
- // Generate a session ID using the /connect/sessions endpoint on the Finch API
30
- // See the docs here https://developer.tryfinch.com/api-reference/connect/new-session#create-a-new-connect-session
31
- sessionId: sessionId.trim(),
32
- // An optional state parameter can be passed
33
- // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12
34
- ...(sendState ? { state: new Date().toISOString() } : {}),
35
- // An optional value for the z-index of the Finch Connect iframe
36
- // Defaults to 999 if not provided
37
- // zIndex: 998,
38
- });
39
- };
40
-
41
- return (
42
- <div className="container">
43
- <h2>
44
- <a href="https://www.npmjs.com/package/@tryfinch/react-connect">@tryfinch/react-connect</a>{' '}
45
- Example App
46
- </h2>
47
- <form className="actions" onSubmit={submissionHandler}>
48
- <div className="row">
49
- <label className="top-label">Session UUID:</label>
50
- <input
51
- type="text"
52
- placeholder="Enter session UUID"
53
- value={sessionId}
54
- onChange={(e) => setSessionId(e.target.value)}
55
- />
56
- </div>
57
- <div className="row">
58
- <label className="top-label">Include State:</label>
59
- <input
60
- type="checkbox"
61
- checked={sendState}
62
- onChange={() => setSendState((prev) => !prev)}
63
- />
64
- </div>
65
- <div className="row">
66
- <button className="cta" type="submit">
67
- Open Finch Connect
68
- </button>
69
- </div>
70
- </form>
71
- <div className="results">
72
- {!result && (
73
- <p>Complete a Finch Connect session and the success event will be displayed here</p>
74
- )}
75
- {result && <Result result={result} />}
76
- </div>
77
- </div>
78
- );
79
- };
80
- export default App;
@@ -1,26 +0,0 @@
1
- import { SuccessEvent, ErrorEvent } from '@tryfinch/react-connect';
2
-
3
- export type ResultContainer = {
4
- kind: 'success';
5
- value: SuccessEvent;
6
- } | {
7
- kind: 'error';
8
- value: ErrorEvent;
9
- } | {
10
- kind: 'closed',
11
- };
12
-
13
- const Result = ({ result }: { result: ResultContainer }) => {
14
- if (result.kind === 'closed') {
15
- return <>
16
- <p>Closed!</p>
17
- </>;
18
- }
19
-
20
- return <>
21
- <p>{ result.kind === 'error' ? 'Error' : 'Success' }:</p>
22
- <pre>{ JSON.stringify(result.value, null, 2) }</pre>
23
- </>;
24
- };
25
-
26
- export default Result
@@ -1,11 +0,0 @@
1
- body {
2
- margin: 0;
3
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
4
- 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
5
- -webkit-font-smoothing: antialiased;
6
- -moz-osx-font-smoothing: grayscale;
7
- }
8
-
9
- code {
10
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
11
- }
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom';
3
-
4
- import './index.css';
5
- import App from './App';
6
-
7
- ReactDOM.render(<App />, document.getElementById('root'));
@@ -1 +0,0 @@
1
- /// <reference types="react-scripts" />
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es5",
4
- "lib": [
5
- "dom",
6
- "dom.iterable",
7
- "esnext"
8
- ],
9
- "allowJs": true,
10
- "skipLibCheck": true,
11
- "esModuleInterop": true,
12
- "allowSyntheticDefaultImports": true,
13
- "strict": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "module": "esnext",
16
- "moduleResolution": "node",
17
- "resolveJsonModule": true,
18
- "isolatedModules": true,
19
- "noEmit": true,
20
- "jsx": "react-jsx",
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": [
24
- "src"
25
- ]
26
- }
package/jest.config.js DELETED
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'jsdom',
4
- roots: ['<rootDir>/src'],
5
- testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'],
6
- collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
7
- };
package/rollup.config.js DELETED
@@ -1,22 +0,0 @@
1
- import external from 'rollup-plugin-peer-deps-external';
2
- import replace from '@rollup/plugin-replace';
3
- import typescript from '@rollup/plugin-typescript';
4
-
5
- import pkg from './package.json';
6
-
7
- const plugins = [
8
- external(),
9
- replace({ SDK_VERSION: pkg.version }),
10
- typescript({ sourceRoot: '.' }),
11
- ];
12
-
13
- export default [
14
- {
15
- input: 'src/index.ts',
16
- output: [
17
- { file: pkg.main, format: 'cjs', sourcemap: true },
18
- { file: pkg.module, format: 'es', sourcemap: true },
19
- ],
20
- plugins,
21
- },
22
- ];
package/src/index.test.ts DELETED
@@ -1,111 +0,0 @@
1
- import { constructAuthUrl, constructPreviewUrl } from './index';
2
-
3
- const NOOP_CALLBACKS = {
4
- onSuccess: jest.fn(),
5
- onError: jest.fn(),
6
- onClose: jest.fn(),
7
- zIndex: 999,
8
- };
9
-
10
- describe('Finch React SDK', () => {
11
- describe('constructAuthUrl', () => {
12
- it('adds the session parameter', () => {
13
- const authUrl = constructAuthUrl({
14
- sessionId: 'test-session-id',
15
- });
16
- expect(authUrl).toContain('session=test-session-id');
17
- });
18
-
19
- it('adds all the expected base parameters to the auth URL', () => {
20
- const expectedParameters = {
21
- app_type: 'spa',
22
- sdk_host_url: encodeURIComponent('http://localhost'),
23
- mode: 'employer',
24
- sdk_version: 'react-SDK_VERSION',
25
- };
26
-
27
- const authUrl = constructAuthUrl({
28
- sessionId: 'test-session-id',
29
- });
30
-
31
- Object.entries(expectedParameters).forEach(([key, value]) => {
32
- expect(authUrl).toContain(`${key}=${value}`);
33
- });
34
- });
35
-
36
- it('adds the state parameter if it is provided', () => {
37
- const testOptions = { sessionId: 'test-session-id', state: 'test-state', ...NOOP_CALLBACKS };
38
- const authUrl = constructAuthUrl(testOptions);
39
-
40
- expect(authUrl).toContain('state=test-state');
41
- });
42
-
43
- it('uses the provided connectUrl if provided', () => {
44
- const authUrl = constructAuthUrl({
45
- sessionId: '123',
46
- apiConfig: {
47
- connectUrl: 'https://cool.site',
48
- },
49
- ...NOOP_CALLBACKS,
50
- });
51
- expect(authUrl.startsWith('https://cool.site/authorize?')).toBe(true);
52
- });
53
- });
54
-
55
- describe('constructPreviewUrl', () => {
56
- it('sets the preview parameter to true', () => {
57
- const previewUrl = constructPreviewUrl({
58
- clientId: 'test-client-id',
59
- products: ['test-product'],
60
- });
61
-
62
- expect(previewUrl).toContain('preview=true');
63
- });
64
-
65
- it('sets the client ID parameter', () => {
66
- const previewUrl = constructPreviewUrl({
67
- clientId: 'test-client-id',
68
- products: ['test-product'],
69
- });
70
-
71
- expect(previewUrl).toContain('client_id=test-client-id');
72
- });
73
-
74
- it('sets the products parameter', () => {
75
- const previewUrl = constructPreviewUrl({
76
- clientId: 'test-client-id',
77
- products: ['test-product', 'test-product-2'],
78
- });
79
-
80
- expect(previewUrl).toContain('products=test-product+test-product-2');
81
- });
82
-
83
- it('sets a default redirect URI', () => {
84
- const expectedRedirectUri = encodeURIComponent('https://www.tryfinch.com');
85
- const previewUrl = constructPreviewUrl({
86
- clientId: 'test-client-id',
87
- products: ['test-product'],
88
- });
89
-
90
- expect(previewUrl).toContain(`redirect_uri=${expectedRedirectUri}`);
91
- });
92
-
93
- it('adds all the expected base parameters to the auth URL', () => {
94
- const expectedParameters = {
95
- app_type: 'spa',
96
- sdk_host_url: encodeURIComponent('http://localhost'),
97
- mode: 'employer',
98
- sdk_version: 'react-SDK_VERSION',
99
- };
100
-
101
- const authUrl = constructPreviewUrl({
102
- clientId: 'test-client-id',
103
- products: ['test-product'],
104
- });
105
-
106
- Object.entries(expectedParameters).forEach(([key, value]) => {
107
- expect(authUrl).toContain(`${key}=${value}`);
108
- });
109
- });
110
- });
111
- });
package/src/index.ts DELETED
@@ -1,253 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
-
3
- export type SuccessEvent = {
4
- code: string;
5
- state?: string;
6
- idpRedirectUri?: string;
7
- };
8
-
9
- type ErrorType = 'validation_error' | 'employer_connection_error';
10
- export type ErrorEvent = {
11
- errorMessage: string;
12
- errorType?: ErrorType;
13
- };
14
-
15
- type ApiConfig = {
16
- connectUrl: string;
17
- };
18
-
19
- export type ConnectInitializeArgs = {
20
- onSuccess: (e: SuccessEvent) => void;
21
- onError: (e: ErrorEvent) => void;
22
- onClose: () => void;
23
- apiConfig?: ApiConfig;
24
- };
25
-
26
- export type ConnectLaunchArgs = {
27
- sessionId: string;
28
- state?: string;
29
- zIndex?: number;
30
- };
31
-
32
- export type ConnectPreviewLaunchArgs = {
33
- clientId: string;
34
- products: string[];
35
- };
36
-
37
- type OpenFn = (args: ConnectLaunchArgs) => void;
38
-
39
- type OpenPreviewFn = (args: ConnectPreviewLaunchArgs) => void;
40
-
41
- const POST_MESSAGE_NAME = 'finch-auth-message-v2' as const;
42
-
43
- type FinchConnectAuthMessage = { name: typeof POST_MESSAGE_NAME } & (
44
- | {
45
- kind: 'closed';
46
- }
47
- | {
48
- kind: 'success';
49
- code: string;
50
- state?: string;
51
- idpRedirectUri?: string;
52
- }
53
- | {
54
- kind: 'error';
55
- error: { shouldClose: boolean; message: string; type: ErrorType };
56
- }
57
- );
58
-
59
- interface FinchConnectPostMessage {
60
- data: FinchConnectAuthMessage;
61
- origin: string;
62
- }
63
-
64
- const BASE_FINCH_CONNECT_URI = 'https://connect.tryfinch.com';
65
-
66
- const FINCH_CONNECT_IFRAME_ID = 'finch-connect-iframe';
67
-
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
- };
76
-
77
- export const constructAuthUrl = ({
78
- sessionId,
79
- state,
80
- apiConfig,
81
- }: {
82
- sessionId: string;
83
- state?: string;
84
- apiConfig?: ApiConfig;
85
- }) => {
86
- const CONNECT_URL = apiConfig?.connectUrl || BASE_FINCH_CONNECT_URI;
87
-
88
- const authUrl = new URL(`${CONNECT_URL}/authorize`);
89
-
90
- authUrl.searchParams.append('session', sessionId);
91
- if (state) authUrl.searchParams.append('state', state);
92
-
93
- appendBaseParams(authUrl);
94
-
95
- return authUrl.href;
96
- };
97
-
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;
108
-
109
- const previewUrl = new URL(`${CONNECT_URL}/authorize`);
110
-
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);
119
-
120
- return previewUrl.href;
121
- };
122
-
123
- let isUseFinchConnectInitialized = false;
124
-
125
- export const useFinchConnect = (
126
- initializeArgs: ConnectInitializeArgs
127
- ): { open: OpenFn; openPreview: OpenPreviewFn } => {
128
- const isHookMounted = useRef(false);
129
-
130
- useEffect(() => {
131
- if (!isHookMounted.current) {
132
- if (isUseFinchConnectInitialized) {
133
- console.error(
134
- 'One useFinchConnect hook has already been registered. Please ensure to only call useFinchConnect once to avoid your event callbacks getting called more than once. You can pass in override options to the open function if you so require.'
135
- );
136
- } else {
137
- isUseFinchConnectInitialized = true;
138
- }
139
-
140
- isHookMounted.current = true;
141
- }
142
- }, []);
143
-
144
- const createAndAttachIFrame = ({
145
- src,
146
- zIndexOverride,
147
- }: {
148
- src: string;
149
- zIndexOverride?: number;
150
- }) => {
151
- if (!document.getElementById(FINCH_CONNECT_IFRAME_ID)) {
152
- const iframe = document.createElement('iframe');
153
- iframe.src = src;
154
- iframe.frameBorder = '0';
155
- iframe.id = FINCH_CONNECT_IFRAME_ID;
156
- iframe.style.position = 'fixed';
157
- iframe.style.zIndex = zIndexOverride?.toString() || '999';
158
- iframe.style.height = '100%';
159
- iframe.style.width = '100%';
160
- iframe.style.top = '0';
161
- iframe.style.backgroundColor = 'none transparent';
162
- iframe.style.border = 'none';
163
- iframe.allow = 'clipboard-write; clipboard-read';
164
- document.body.prepend(iframe);
165
- document.body.style.overflow = 'hidden';
166
- }
167
- };
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
-
194
- const close = () => {
195
- const frameToRemove = document.getElementById(FINCH_CONNECT_IFRAME_ID);
196
- if (frameToRemove) {
197
- frameToRemove.parentNode?.removeChild(frameToRemove);
198
- document.body.style.overflow = 'inherit';
199
- }
200
- };
201
-
202
- useEffect(() => {
203
- function handleFinchAuth(event: FinchConnectPostMessage) {
204
- const CONNECT_URL = initializeArgs.apiConfig?.connectUrl || BASE_FINCH_CONNECT_URI;
205
-
206
- if (!event.data) return;
207
- if (event.data.name !== POST_MESSAGE_NAME) return;
208
- if (!event.origin.startsWith(CONNECT_URL)) return;
209
-
210
- if (event.data.kind !== 'error') close();
211
-
212
- switch (event.data.kind) {
213
- case 'closed':
214
- initializeArgs.onClose();
215
- break;
216
- case 'error':
217
- if (event.data.error?.shouldClose) close();
218
-
219
- initializeArgs.onError({
220
- errorMessage: event.data.error?.message,
221
- errorType: event.data.error?.type,
222
- });
223
- break;
224
- case 'success':
225
- initializeArgs.onSuccess({
226
- code: event.data.code,
227
- state: event.data.state,
228
- idpRedirectUri: event.data.idpRedirectUri,
229
- });
230
- break;
231
- default: {
232
- // This case should never happen, if it does it should be reported to us
233
- initializeArgs.onError({
234
- errorMessage: `Report to developers@tryfinch.com: unable to handle window.postMessage for: ${JSON.stringify(
235
- event.data
236
- )}`,
237
- });
238
- }
239
- }
240
- }
241
-
242
- window.addEventListener('message', handleFinchAuth);
243
- return () => {
244
- window.removeEventListener('message', handleFinchAuth);
245
- isUseFinchConnectInitialized = false;
246
- };
247
- }, [initializeArgs.onClose, initializeArgs.onError, initializeArgs.onSuccess]);
248
-
249
- return {
250
- open,
251
- openPreview,
252
- };
253
- };
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2016",
4
- "module": "esnext",
5
- "declaration": true,
6
- "declarationMap": true,
7
- "sourceMap": true,
8
- "outDir": "./",
9
- "esModuleInterop": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "strict": true
12
- },
13
- "include": ["src/**/*"],
14
- "exclude": ["node_modules", "example/**/*"]
15
- }