@quiltt/react 4.3.1 → 4.3.2
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/CHANGELOG.md +10 -0
- package/dist/{QuilttAuthProvider-12s-BZuGySu0.js → QuilttAuthProvider-12s-D-Wr7LUT.js} +12 -4
- package/dist/index.js +10 -8
- package/dist/{useQuilttConnector-12s-CL6uHdqe.js → useQuilttConnector-12s-BJljrsWT.js} +63 -41
- package/dist/{useQuilttInstitutions-12s-FLdw-CQZ.js → useQuilttInstitutions-12s-PJd2C3BF.js} +10 -7
- package/dist/{useQuilttSession-12s-CM6ALGSN.js → useQuilttSession-12s-CwVw-aOM.js} +1 -1
- package/package.json +2 -2
- package/src/components/QuilttButton.tsx +5 -3
- package/src/hooks/useQuilttConnector.ts +77 -47
- package/src/hooks/useQuilttInstitutions.ts +13 -10
- package/src/providers/QuilttAuthProvider.tsx +9 -2
- package/src/utils/isDeepEqual.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# @quiltt/react
|
|
2
2
|
|
|
3
|
+
## 4.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#372](https://github.com/quiltt/quiltt-js/pull/372) [`c022ebe`](https://github.com/quiltt/quiltt-js/commit/c022ebed0c82404a1bdbf5abbaf6e60b49f2d07a) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Hardened React SDK against unstable prop references by implementing ref-based callback wrappers and deep equality checks, eliminating unnecessary re-renders, event handler churn, and API calls without requiring customers to use useCallback.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`c022ebe`](https://github.com/quiltt/quiltt-js/commit/c022ebed0c82404a1bdbf5abbaf6e60b49f2d07a)]:
|
|
10
|
+
- @quiltt/core@4.3.2
|
|
11
|
+
|
|
3
12
|
## 4.3.1
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
6
15
|
|
|
7
16
|
- [#366](https://github.com/quiltt/quiltt-js/pull/366) [`dc376b5`](https://github.com/quiltt/quiltt-js/commit/dc376b52dd824d7867ca74677bbfd5c54eff5cdc) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Warn if useQuilttConnector is unmounted while in use
|
|
17
|
+
- [#365](https://github.com/quiltt/quiltt-js/pull/365) [`5f6b8af`](https://github.com/quiltt/quiltt-js/commit/5f6b8af153086c77bb2227d43ae7023fb0c47985) Thanks [@rubendinho](https://github.com/rubendinho)! - Standardize SDK agent tracking header to `Quiltt-SDK-Agent` and add support for custom Apollo Links injection
|
|
8
18
|
|
|
9
19
|
- Updated dependencies [[`dc376b5`](https://github.com/quiltt/quiltt-js/commit/dc376b52dd824d7867ca74677bbfd5c54eff5cdc)]:
|
|
10
20
|
- @quiltt/core@4.3.1
|
|
@@ -7,7 +7,7 @@ import './useSession-12s-7GOn4sUn.js';
|
|
|
7
7
|
import 'use-debounce';
|
|
8
8
|
import { jsx } from 'react/jsx-runtime';
|
|
9
9
|
import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js';
|
|
10
|
-
import { u as useQuilttSession } from './useQuilttSession-12s-
|
|
10
|
+
import { u as useQuilttSession } from './useQuilttSession-12s-CwVw-aOM.js';
|
|
11
11
|
|
|
12
12
|
const useAuthenticateSession = (auth, setSession)=>{
|
|
13
13
|
const authenticateSession = useCallback(async (payload, callbacks)=>{
|
|
@@ -133,8 +133,9 @@ const useRevokeSession = (auth, session, setSession)=>{
|
|
|
133
133
|
}
|
|
134
134
|
if (obj1 instanceof Set && obj2 instanceof Set) {
|
|
135
135
|
if (obj1.size !== obj2.size) return false;
|
|
136
|
+
const arr2 = Array.from(obj2);
|
|
136
137
|
for (const item of obj1){
|
|
137
|
-
if (!
|
|
138
|
+
if (!arr2.some((value)=>isDeepEqual(item, value))) return false;
|
|
138
139
|
}
|
|
139
140
|
return true;
|
|
140
141
|
}
|
|
@@ -161,15 +162,22 @@ const useRevokeSession = (auth, session, setSession)=>{
|
|
|
161
162
|
*/ const QuilttAuthProvider = ({ graphqlClient, token, children })=>{
|
|
162
163
|
const { session, importSession } = useQuilttSession();
|
|
163
164
|
const previousSessionRef = useRef(session);
|
|
165
|
+
const previousTokenRef = useRef();
|
|
164
166
|
// Memoize the client to avoid unnecessary re-renders
|
|
165
167
|
const apolloClient = useMemo(()=>graphqlClient || new QuilttClient({
|
|
166
168
|
cache: new InMemoryCache()
|
|
167
169
|
}), [
|
|
168
170
|
graphqlClient
|
|
169
171
|
]);
|
|
170
|
-
// Import passed in token
|
|
172
|
+
// Import passed in token (only if value has changed)
|
|
171
173
|
useEffect(()=>{
|
|
172
|
-
if (token
|
|
174
|
+
if (token && token !== previousTokenRef.current) {
|
|
175
|
+
importSession(token);
|
|
176
|
+
previousTokenRef.current = token;
|
|
177
|
+
} else if (!token) {
|
|
178
|
+
// Reset ref when token becomes undefined to allow re-import of same token later
|
|
179
|
+
previousTokenRef.current = undefined;
|
|
180
|
+
}
|
|
173
181
|
}, [
|
|
174
182
|
token,
|
|
175
183
|
importSession
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export * from '@quiltt/core';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { useRef, useEffect } from 'react';
|
|
4
|
-
import { u as useQuilttConnector } from './useQuilttConnector-12s-
|
|
5
|
-
import { i as isDeepEqual, Q as QuilttAuthProvider } from './QuilttAuthProvider-12s-
|
|
6
|
-
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-12s-
|
|
4
|
+
import { u as useQuilttConnector } from './useQuilttConnector-12s-BJljrsWT.js';
|
|
5
|
+
import { i as isDeepEqual, Q as QuilttAuthProvider } from './QuilttAuthProvider-12s-D-Wr7LUT.js';
|
|
6
|
+
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-12s-D-Wr7LUT.js';
|
|
7
7
|
export { u as useEventListener } from './useEventListener-12s-D_-6QIXa.js';
|
|
8
8
|
export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-12s-DeTHOKz1.js';
|
|
9
9
|
export { u as useQuilttClient } from './useQuilttClient-12s-CAAUait1.js';
|
|
10
|
-
export { u as useQuilttInstitutions } from './useQuilttInstitutions-12s-
|
|
11
|
-
export { u as useQuilttSession } from './useQuilttSession-12s-
|
|
10
|
+
export { u as useQuilttInstitutions } from './useQuilttInstitutions-12s-PJd2C3BF.js';
|
|
11
|
+
export { u as useQuilttSession } from './useQuilttSession-12s-CwVw-aOM.js';
|
|
12
12
|
export { u as useQuilttSettings } from './useQuilttSettings-12s--rCJoNHD.js';
|
|
13
13
|
export { u as useSession } from './useSession-12s-7GOn4sUn.js';
|
|
14
14
|
export { u as useStorage } from './useStorage-12s-DHcq3Kuh.js';
|
|
@@ -78,9 +78,11 @@ import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-12s-ZcmFmO
|
|
|
78
78
|
// 1. Pre-open validation
|
|
79
79
|
// 2. Preventing opening via event.preventDefault()
|
|
80
80
|
// 3. Setting up state before connector opens
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
81
|
+
onClick?.(event);
|
|
82
|
+
// Only open if event wasn't prevented
|
|
83
|
+
if (!event.defaultPrevented) {
|
|
84
|
+
open();
|
|
85
|
+
}
|
|
84
86
|
};
|
|
85
87
|
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
86
88
|
const buttonKey = props.key ?? (forceRemountOnConnectionChange ? `${connectorId}-${connectionId || 'no-connection'}` : undefined);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { cdnBase } from '@quiltt/core';
|
|
4
|
-
import { u as useQuilttSession } from './useQuilttSession-12s-
|
|
4
|
+
import { u as useQuilttSession } from './useQuilttSession-12s-CwVw-aOM.js';
|
|
5
5
|
import { u as useScript } from './useScript-12s-JCgaTW9n.js';
|
|
6
|
+
import { i as isDeepEqual } from './QuilttAuthProvider-12s-D-Wr7LUT.js';
|
|
6
7
|
|
|
7
|
-
var version = "4.3.
|
|
8
|
+
var version = "4.3.2";
|
|
8
9
|
|
|
9
10
|
const useQuilttConnector = (connectorId, options)=>{
|
|
10
11
|
const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`, {
|
|
@@ -18,9 +19,15 @@ const useQuilttConnector = (connectorId, options)=>{
|
|
|
18
19
|
// Keep track of the previous connectionId to detect changes
|
|
19
20
|
const prevConnectionIdRef = useRef(options?.connectionId);
|
|
20
21
|
const prevConnectorIdRef = useRef(connectorId);
|
|
22
|
+
const prevInstitutionRef = useRef(options?.institution);
|
|
21
23
|
const connectorCreatedRef = useRef(false);
|
|
22
24
|
// Track whether the connector is currently open
|
|
23
25
|
const isConnectorOpenRef = useRef(false);
|
|
26
|
+
// Store callbacks in refs to maintain stable references
|
|
27
|
+
const callbacksRef = useRef(options || {});
|
|
28
|
+
useEffect(()=>{
|
|
29
|
+
callbacksRef.current = options || {};
|
|
30
|
+
});
|
|
24
31
|
// Set Session
|
|
25
32
|
// biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
|
|
26
33
|
useEffect(()=>{
|
|
@@ -36,10 +43,11 @@ const useQuilttConnector = (connectorId, options)=>{
|
|
|
36
43
|
if (typeof Quiltt === 'undefined' || !connectorId) return;
|
|
37
44
|
const currentConnectionId = options?.connectionId;
|
|
38
45
|
const currentInstitution = options?.institution;
|
|
39
|
-
// Check for changes
|
|
46
|
+
// Check for changes - use deep equality for institution object
|
|
40
47
|
const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId;
|
|
41
48
|
const connectorIdChanged = prevConnectorIdRef.current !== connectorId;
|
|
42
|
-
const
|
|
49
|
+
const institutionChanged = !isDeepEqual(prevInstitutionRef.current, currentInstitution);
|
|
50
|
+
const hasChanges = connectionIdChanged || connectorIdChanged || institutionChanged || !connectorCreatedRef.current;
|
|
43
51
|
// Update if there are changes, regardless of what the changes are
|
|
44
52
|
if (hasChanges) {
|
|
45
53
|
if (currentConnectionId) {
|
|
@@ -57,6 +65,7 @@ const useQuilttConnector = (connectorId, options)=>{
|
|
|
57
65
|
connectorCreatedRef.current = true;
|
|
58
66
|
prevConnectionIdRef.current = currentConnectionId;
|
|
59
67
|
prevConnectorIdRef.current = connectorId;
|
|
68
|
+
prevInstitutionRef.current = currentInstitution;
|
|
60
69
|
}
|
|
61
70
|
}, [
|
|
62
71
|
connectorId,
|
|
@@ -64,56 +73,69 @@ const useQuilttConnector = (connectorId, options)=>{
|
|
|
64
73
|
options?.institution,
|
|
65
74
|
status
|
|
66
75
|
]);
|
|
67
|
-
// Internal handlers to track connector state
|
|
76
|
+
// Internal handlers to track connector state (stable references)
|
|
68
77
|
const handleOpen = useCallback((metadata)=>{
|
|
69
78
|
isConnectorOpenRef.current = true;
|
|
70
|
-
|
|
71
|
-
}, [
|
|
72
|
-
options?.onOpen
|
|
73
|
-
]);
|
|
79
|
+
callbacksRef.current?.onOpen?.(metadata);
|
|
80
|
+
}, []);
|
|
74
81
|
const handleExit = useCallback((type, metadata)=>{
|
|
75
82
|
isConnectorOpenRef.current = false;
|
|
76
|
-
|
|
77
|
-
}, [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
callbacksRef.current?.onExit?.(type, metadata);
|
|
84
|
+
}, []);
|
|
85
|
+
// Create stable wrapper functions for callbacks
|
|
86
|
+
const stableOnEvent = useCallback((type, metadata)=>{
|
|
87
|
+
callbacksRef.current?.onEvent?.(type, metadata);
|
|
88
|
+
}, []);
|
|
89
|
+
const stableOnLoad = useCallback((metadata)=>{
|
|
90
|
+
callbacksRef.current?.onLoad?.(metadata);
|
|
91
|
+
}, []);
|
|
92
|
+
const stableOnExitSuccess = useCallback((metadata)=>{
|
|
93
|
+
callbacksRef.current?.onExitSuccess?.(metadata);
|
|
94
|
+
}, []);
|
|
95
|
+
const stableOnExitAbort = useCallback((metadata)=>{
|
|
96
|
+
callbacksRef.current?.onExitAbort?.(metadata);
|
|
97
|
+
}, []);
|
|
98
|
+
const stableOnExitError = useCallback((metadata)=>{
|
|
99
|
+
callbacksRef.current?.onExitError?.(metadata);
|
|
100
|
+
}, []);
|
|
101
|
+
// Register event handlers (only re-runs when connector changes)
|
|
81
102
|
useEffect(()=>{
|
|
82
103
|
if (!connector) return;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
// Capture which handlers we're registering to ensure proper cleanup
|
|
105
|
+
const registered = {
|
|
106
|
+
onEvent: callbacksRef.current?.onEvent ? stableOnEvent : null,
|
|
107
|
+
onOpen: callbacksRef.current?.onOpen ? handleOpen : null,
|
|
108
|
+
onLoad: callbacksRef.current?.onLoad ? stableOnLoad : null,
|
|
109
|
+
onExit: callbacksRef.current?.onExit ? handleExit : null,
|
|
110
|
+
onExitSuccess: callbacksRef.current?.onExitSuccess ? stableOnExitSuccess : null,
|
|
111
|
+
onExitAbort: callbacksRef.current?.onExitAbort ? stableOnExitAbort : null,
|
|
112
|
+
onExitError: callbacksRef.current?.onExitError ? stableOnExitError : null
|
|
91
113
|
};
|
|
92
|
-
if (
|
|
93
|
-
if (
|
|
94
|
-
if (
|
|
95
|
-
if (
|
|
96
|
-
if (
|
|
97
|
-
if (
|
|
98
|
-
if (
|
|
114
|
+
if (registered.onEvent) connector.onEvent(registered.onEvent);
|
|
115
|
+
if (registered.onOpen) connector.onOpen(registered.onOpen);
|
|
116
|
+
if (registered.onLoad) connector.onLoad(registered.onLoad);
|
|
117
|
+
if (registered.onExit) connector.onExit(registered.onExit);
|
|
118
|
+
if (registered.onExitSuccess) connector.onExitSuccess(registered.onExitSuccess);
|
|
119
|
+
if (registered.onExitAbort) connector.onExitAbort(registered.onExitAbort);
|
|
120
|
+
if (registered.onExitError) connector.onExitError(registered.onExitError);
|
|
99
121
|
return ()=>{
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
102
|
-
if (
|
|
103
|
-
if (
|
|
104
|
-
if (
|
|
105
|
-
if (
|
|
106
|
-
if (
|
|
122
|
+
if (registered.onEvent) connector.offEvent(registered.onEvent);
|
|
123
|
+
if (registered.onOpen) connector.offOpen(registered.onOpen);
|
|
124
|
+
if (registered.onLoad) connector.offLoad(registered.onLoad);
|
|
125
|
+
if (registered.onExit) connector.offExit(registered.onExit);
|
|
126
|
+
if (registered.onExitSuccess) connector.offExitSuccess(registered.onExitSuccess);
|
|
127
|
+
if (registered.onExitAbort) connector.offExitAbort(registered.onExitAbort);
|
|
128
|
+
if (registered.onExitError) connector.offExitError(registered.onExitError);
|
|
107
129
|
};
|
|
108
130
|
}, [
|
|
109
131
|
connector,
|
|
110
|
-
|
|
132
|
+
stableOnEvent,
|
|
111
133
|
handleOpen,
|
|
112
|
-
|
|
134
|
+
stableOnLoad,
|
|
113
135
|
handleExit,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
stableOnExitSuccess,
|
|
137
|
+
stableOnExitAbort,
|
|
138
|
+
stableOnExitError
|
|
117
139
|
]);
|
|
118
140
|
// This is used to hide any potential race conditions from usage; allowing
|
|
119
141
|
// interaction before the script may have loaded.
|
package/dist/{useQuilttInstitutions-12s-FLdw-CQZ.js → useQuilttInstitutions-12s-PJd2C3BF.js}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useMemo, useState,
|
|
2
|
+
import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { InstitutionsAPI } from '@quiltt/core';
|
|
4
4
|
import { useDebounce } from 'use-debounce';
|
|
5
|
-
import { v as version } from './useQuilttConnector-12s-
|
|
5
|
+
import { v as version } from './useQuilttConnector-12s-BJljrsWT.js';
|
|
6
6
|
import { u as useSession } from './useSession-12s-7GOn4sUn.js';
|
|
7
7
|
|
|
8
8
|
const useQuilttInstitutions = (connectorId, onErrorCallback)=>{
|
|
@@ -27,6 +27,11 @@ const useQuilttInstitutions = (connectorId, onErrorCallback)=>{
|
|
|
27
27
|
const [searchTerm] = useDebounce(searchTermInput, 350);
|
|
28
28
|
const [searchResults, setSearchResults] = useState([]);
|
|
29
29
|
const [isSearching, setIsSearching] = useState(false);
|
|
30
|
+
// Store callback in ref to maintain stable reference
|
|
31
|
+
const onErrorCallbackRef = useRef(onErrorCallback);
|
|
32
|
+
useEffect(()=>{
|
|
33
|
+
onErrorCallbackRef.current = onErrorCallback;
|
|
34
|
+
});
|
|
30
35
|
/**
|
|
31
36
|
* Start Search
|
|
32
37
|
* This function is used to initiate a search for institutions based on the provided term with
|
|
@@ -43,10 +48,8 @@ const useQuilttInstitutions = (connectorId, onErrorCallback)=>{
|
|
|
43
48
|
const handleError = useCallback((message)=>{
|
|
44
49
|
const errorMessage = message || 'Unknown error occurred while searching institutions';
|
|
45
50
|
console.error('Quiltt Institutions Search Error:', errorMessage);
|
|
46
|
-
if (
|
|
47
|
-
}, [
|
|
48
|
-
onErrorCallback
|
|
49
|
-
]);
|
|
51
|
+
if (onErrorCallbackRef.current) onErrorCallbackRef.current(errorMessage);
|
|
52
|
+
}, []);
|
|
50
53
|
/**
|
|
51
54
|
* Run Search
|
|
52
55
|
* This effect will run when the searchTerm changes and is at least 2 characters long.
|
|
@@ -66,7 +69,7 @@ const useQuilttInstitutions = (connectorId, onErrorCallback)=>{
|
|
|
66
69
|
}
|
|
67
70
|
}).catch((error)=>{
|
|
68
71
|
if (!abortController.signal.aborted) {
|
|
69
|
-
handleError(error
|
|
72
|
+
handleError(error?.message);
|
|
70
73
|
setIsSearching(false);
|
|
71
74
|
}
|
|
72
75
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { AuthAPI } from '@quiltt/core';
|
|
4
|
-
import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-12s-
|
|
4
|
+
import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-12s-D-Wr7LUT.js';
|
|
5
5
|
import { u as useQuilttSettings } from './useQuilttSettings-12s--rCJoNHD.js';
|
|
6
6
|
import { u as useSession } from './useSession-12s-7GOn4sUn.js';
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/react",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.2",
|
|
4
4
|
"description": "React Components and Hooks for Quiltt Connector",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quiltt",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@apollo/client": "^3.14.0",
|
|
38
38
|
"use-debounce": "^10.0.4",
|
|
39
|
-
"@quiltt/core": "4.3.
|
|
39
|
+
"@quiltt/core": "4.3.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "2.2.4",
|
|
@@ -127,10 +127,12 @@ export const QuilttButton = <T extends ElementType = 'button'>({
|
|
|
127
127
|
// 1. Pre-open validation
|
|
128
128
|
// 2. Preventing opening via event.preventDefault()
|
|
129
129
|
// 3. Setting up state before connector opens
|
|
130
|
-
|
|
130
|
+
onClick?.(event)
|
|
131
131
|
|
|
132
|
-
//
|
|
133
|
-
|
|
132
|
+
// Only open if event wasn't prevented
|
|
133
|
+
if (!event.defaultPrevented) {
|
|
134
|
+
open()
|
|
135
|
+
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
@@ -11,6 +11,7 @@ import { cdnBase } from '@quiltt/core'
|
|
|
11
11
|
|
|
12
12
|
import { useQuilttSession } from '@/hooks/useQuilttSession'
|
|
13
13
|
import { useScript } from '@/hooks/useScript'
|
|
14
|
+
import { isDeepEqual } from '@/utils/isDeepEqual'
|
|
14
15
|
import { version } from '@/version'
|
|
15
16
|
|
|
16
17
|
declare const Quiltt: ConnectorSDK
|
|
@@ -33,11 +34,18 @@ export const useQuilttConnector = (
|
|
|
33
34
|
// Keep track of the previous connectionId to detect changes
|
|
34
35
|
const prevConnectionIdRef = useRef<string | undefined>(options?.connectionId)
|
|
35
36
|
const prevConnectorIdRef = useRef<string | undefined>(connectorId)
|
|
37
|
+
const prevInstitutionRef = useRef<string | undefined>(options?.institution)
|
|
36
38
|
const connectorCreatedRef = useRef<boolean>(false)
|
|
37
39
|
|
|
38
40
|
// Track whether the connector is currently open
|
|
39
41
|
const isConnectorOpenRef = useRef<boolean>(false)
|
|
40
42
|
|
|
43
|
+
// Store callbacks in refs to maintain stable references
|
|
44
|
+
const callbacksRef = useRef<ConnectorSDKConnectorOptions>(options || {})
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
callbacksRef.current = options || {}
|
|
47
|
+
})
|
|
48
|
+
|
|
41
49
|
// Set Session
|
|
42
50
|
// biome-ignore lint/correctness/useExhaustiveDependencies: trigger effects when script status changes too
|
|
43
51
|
useEffect(() => {
|
|
@@ -54,10 +62,15 @@ export const useQuilttConnector = (
|
|
|
54
62
|
const currentConnectionId = options?.connectionId
|
|
55
63
|
const currentInstitution = options?.institution
|
|
56
64
|
|
|
57
|
-
// Check for changes
|
|
65
|
+
// Check for changes - use deep equality for institution object
|
|
58
66
|
const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId
|
|
59
67
|
const connectorIdChanged = prevConnectorIdRef.current !== connectorId
|
|
60
|
-
const
|
|
68
|
+
const institutionChanged = !isDeepEqual(prevInstitutionRef.current, currentInstitution)
|
|
69
|
+
const hasChanges =
|
|
70
|
+
connectionIdChanged ||
|
|
71
|
+
connectorIdChanged ||
|
|
72
|
+
institutionChanged ||
|
|
73
|
+
!connectorCreatedRef.current
|
|
61
74
|
|
|
62
75
|
// Update if there are changes, regardless of what the changes are
|
|
63
76
|
if (hasChanges) {
|
|
@@ -73,66 +86,83 @@ export const useQuilttConnector = (
|
|
|
73
86
|
connectorCreatedRef.current = true
|
|
74
87
|
prevConnectionIdRef.current = currentConnectionId
|
|
75
88
|
prevConnectorIdRef.current = connectorId
|
|
89
|
+
prevInstitutionRef.current = currentInstitution
|
|
76
90
|
}
|
|
77
91
|
}, [connectorId, options?.connectionId, options?.institution, status])
|
|
78
92
|
|
|
79
|
-
// Internal handlers to track connector state
|
|
80
|
-
const handleOpen = useCallback(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// Internal handlers to track connector state (stable references)
|
|
94
|
+
const handleOpen = useCallback((metadata: any) => {
|
|
95
|
+
isConnectorOpenRef.current = true
|
|
96
|
+
callbacksRef.current?.onOpen?.(metadata)
|
|
97
|
+
}, [])
|
|
98
|
+
|
|
99
|
+
const handleExit = useCallback((type: any, metadata: any) => {
|
|
100
|
+
isConnectorOpenRef.current = false
|
|
101
|
+
callbacksRef.current?.onExit?.(type, metadata)
|
|
102
|
+
}, [])
|
|
103
|
+
|
|
104
|
+
// Create stable wrapper functions for callbacks
|
|
105
|
+
const stableOnEvent = useCallback((type: any, metadata: any) => {
|
|
106
|
+
callbacksRef.current?.onEvent?.(type, metadata)
|
|
107
|
+
}, [])
|
|
108
|
+
|
|
109
|
+
const stableOnLoad = useCallback((metadata: any) => {
|
|
110
|
+
callbacksRef.current?.onLoad?.(metadata)
|
|
111
|
+
}, [])
|
|
112
|
+
|
|
113
|
+
const stableOnExitSuccess = useCallback((metadata: any) => {
|
|
114
|
+
callbacksRef.current?.onExitSuccess?.(metadata)
|
|
115
|
+
}, [])
|
|
116
|
+
|
|
117
|
+
const stableOnExitAbort = useCallback((metadata: any) => {
|
|
118
|
+
callbacksRef.current?.onExitAbort?.(metadata)
|
|
119
|
+
}, [])
|
|
120
|
+
|
|
121
|
+
const stableOnExitError = useCallback((metadata: any) => {
|
|
122
|
+
callbacksRef.current?.onExitError?.(metadata)
|
|
123
|
+
}, [])
|
|
124
|
+
|
|
125
|
+
// Register event handlers (only re-runs when connector changes)
|
|
97
126
|
useEffect(() => {
|
|
98
127
|
if (!connector) return
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
// Capture which handlers we're registering to ensure proper cleanup
|
|
130
|
+
const registered = {
|
|
131
|
+
onEvent: callbacksRef.current?.onEvent ? stableOnEvent : null,
|
|
132
|
+
onOpen: callbacksRef.current?.onOpen ? handleOpen : null,
|
|
133
|
+
onLoad: callbacksRef.current?.onLoad ? stableOnLoad : null,
|
|
134
|
+
onExit: callbacksRef.current?.onExit ? handleExit : null,
|
|
135
|
+
onExitSuccess: callbacksRef.current?.onExitSuccess ? stableOnExitSuccess : null,
|
|
136
|
+
onExitAbort: callbacksRef.current?.onExitAbort ? stableOnExitAbort : null,
|
|
137
|
+
onExitError: callbacksRef.current?.onExitError ? stableOnExitError : null,
|
|
108
138
|
}
|
|
109
139
|
|
|
110
|
-
if (
|
|
111
|
-
if (
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
115
|
-
if (
|
|
116
|
-
if (
|
|
140
|
+
if (registered.onEvent) connector.onEvent(registered.onEvent)
|
|
141
|
+
if (registered.onOpen) connector.onOpen(registered.onOpen)
|
|
142
|
+
if (registered.onLoad) connector.onLoad(registered.onLoad)
|
|
143
|
+
if (registered.onExit) connector.onExit(registered.onExit)
|
|
144
|
+
if (registered.onExitSuccess) connector.onExitSuccess(registered.onExitSuccess)
|
|
145
|
+
if (registered.onExitAbort) connector.onExitAbort(registered.onExitAbort)
|
|
146
|
+
if (registered.onExitError) connector.onExitError(registered.onExitError)
|
|
117
147
|
|
|
118
148
|
return () => {
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
122
|
-
if (
|
|
123
|
-
if (
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
149
|
+
if (registered.onEvent) connector.offEvent(registered.onEvent)
|
|
150
|
+
if (registered.onOpen) connector.offOpen(registered.onOpen)
|
|
151
|
+
if (registered.onLoad) connector.offLoad(registered.onLoad)
|
|
152
|
+
if (registered.onExit) connector.offExit(registered.onExit)
|
|
153
|
+
if (registered.onExitSuccess) connector.offExitSuccess(registered.onExitSuccess)
|
|
154
|
+
if (registered.onExitAbort) connector.offExitAbort(registered.onExitAbort)
|
|
155
|
+
if (registered.onExitError) connector.offExitError(registered.onExitError)
|
|
126
156
|
}
|
|
127
157
|
}, [
|
|
128
158
|
connector,
|
|
129
|
-
|
|
159
|
+
stableOnEvent,
|
|
130
160
|
handleOpen,
|
|
131
|
-
|
|
161
|
+
stableOnLoad,
|
|
132
162
|
handleExit,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
163
|
+
stableOnExitSuccess,
|
|
164
|
+
stableOnExitAbort,
|
|
165
|
+
stableOnExitError,
|
|
136
166
|
])
|
|
137
167
|
|
|
138
168
|
// This is used to hide any potential race conditions from usage; allowing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
4
|
|
|
5
5
|
import type { ErrorData, InstitutionsData } from '@quiltt/core'
|
|
6
6
|
import { InstitutionsAPI } from '@quiltt/core'
|
|
@@ -53,6 +53,12 @@ export const useQuilttInstitutions: UseQuilttInstitutions = (connectorId, onErro
|
|
|
53
53
|
|
|
54
54
|
const [isSearching, setIsSearching] = useState(false)
|
|
55
55
|
|
|
56
|
+
// Store callback in ref to maintain stable reference
|
|
57
|
+
const onErrorCallbackRef = useRef(onErrorCallback)
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
onErrorCallbackRef.current = onErrorCallback
|
|
60
|
+
})
|
|
61
|
+
|
|
56
62
|
/**
|
|
57
63
|
* Start Search
|
|
58
64
|
* This function is used to initiate a search for institutions based on the provided term with
|
|
@@ -69,15 +75,12 @@ export const useQuilttInstitutions: UseQuilttInstitutions = (connectorId, onErro
|
|
|
69
75
|
setSearchTermInput(term)
|
|
70
76
|
}, [])
|
|
71
77
|
|
|
72
|
-
const handleError = useCallback(
|
|
73
|
-
|
|
74
|
-
const errorMessage = message || 'Unknown error occurred while searching institutions'
|
|
78
|
+
const handleError = useCallback((message: string) => {
|
|
79
|
+
const errorMessage = message || 'Unknown error occurred while searching institutions'
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
[onErrorCallback]
|
|
80
|
-
)
|
|
81
|
+
console.error('Quiltt Institutions Search Error:', errorMessage)
|
|
82
|
+
if (onErrorCallbackRef.current) onErrorCallbackRef.current(errorMessage)
|
|
83
|
+
}, [])
|
|
81
84
|
|
|
82
85
|
/**
|
|
83
86
|
* Run Search
|
|
@@ -104,7 +107,7 @@ export const useQuilttInstitutions: UseQuilttInstitutions = (connectorId, onErro
|
|
|
104
107
|
})
|
|
105
108
|
.catch((error) => {
|
|
106
109
|
if (!abortController.signal.aborted) {
|
|
107
|
-
handleError(error
|
|
110
|
+
handleError(error?.message)
|
|
108
111
|
setIsSearching(false)
|
|
109
112
|
}
|
|
110
113
|
})
|
|
@@ -29,6 +29,7 @@ export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({
|
|
|
29
29
|
}) => {
|
|
30
30
|
const { session, importSession } = useQuilttSession()
|
|
31
31
|
const previousSessionRef = useRef(session)
|
|
32
|
+
const previousTokenRef = useRef<string | undefined>()
|
|
32
33
|
|
|
33
34
|
// Memoize the client to avoid unnecessary re-renders
|
|
34
35
|
const apolloClient = useMemo(
|
|
@@ -40,9 +41,15 @@ export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({
|
|
|
40
41
|
[graphqlClient]
|
|
41
42
|
)
|
|
42
43
|
|
|
43
|
-
// Import passed in token
|
|
44
|
+
// Import passed in token (only if value has changed)
|
|
44
45
|
useEffect(() => {
|
|
45
|
-
if (token
|
|
46
|
+
if (token && token !== previousTokenRef.current) {
|
|
47
|
+
importSession(token)
|
|
48
|
+
previousTokenRef.current = token
|
|
49
|
+
} else if (!token) {
|
|
50
|
+
// Reset ref when token becomes undefined to allow re-import of same token later
|
|
51
|
+
previousTokenRef.current = undefined
|
|
52
|
+
}
|
|
46
53
|
}, [token, importSession])
|
|
47
54
|
|
|
48
55
|
// Reset Client Store when session changes (using deep comparison)
|
package/src/utils/isDeepEqual.ts
CHANGED
|
@@ -31,8 +31,9 @@ export const isDeepEqual = (obj1: unknown, obj2: unknown): boolean => {
|
|
|
31
31
|
}
|
|
32
32
|
if (obj1 instanceof Set && obj2 instanceof Set) {
|
|
33
33
|
if (obj1.size !== obj2.size) return false
|
|
34
|
+
const arr2 = Array.from(obj2)
|
|
34
35
|
for (const item of obj1) {
|
|
35
|
-
if (!
|
|
36
|
+
if (!arr2.some((value) => isDeepEqual(item, value))) return false
|
|
36
37
|
}
|
|
37
38
|
return true
|
|
38
39
|
}
|