@quiltt/react 4.1.0 → 4.2.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/CHANGELOG.md +11 -0
- package/dist/{QuilttAuthProvider-client-CER-TOln.js → QuilttAuthProvider-client-DLARZukU.js} +2 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +100 -8
- package/dist/{useQuilttConnector-client-BB7kw8vs.js → useQuilttConnector-client-BK7ybRZe.js} +42 -15
- package/dist/{useQuilttSession-client-D2mjVT4S.js → useQuilttSession-client-Ddb55W0n.js} +1 -1
- package/package.json +2 -2
- package/src/components/QuilttButton.tsx +77 -1
- package/src/components/QuilttContainer.tsx +71 -1
- package/src/hooks/useQuilttConnector.ts +42 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @quiltt/react
|
|
2
2
|
|
|
3
|
+
## 4.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#348](https://github.com/quiltt/quiltt-js/pull/348) [`7e27845`](https://github.com/quiltt/quiltt-js/commit/7e2784523124c87fc6654d8336f924286daade1b) Thanks [@zubairaziz](https://github.com/zubairaziz)! - resolve connectionId persistence issue in QuilttContainer
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`7e27845`](https://github.com/quiltt/quiltt-js/commit/7e2784523124c87fc6654d8336f924286daade1b)]:
|
|
12
|
+
- @quiltt/core@4.2.0
|
|
13
|
+
|
|
3
14
|
## 4.1.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/dist/{QuilttAuthProvider-client-CER-TOln.js → QuilttAuthProvider-client-DLARZukU.js}
RENAMED
|
@@ -6,7 +6,7 @@ import './QuilttSettings-client-BK-0SQME.js';
|
|
|
6
6
|
import './useSession-client-CCAvnROP.js';
|
|
7
7
|
import { jsx } from 'react/jsx-runtime';
|
|
8
8
|
import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js';
|
|
9
|
-
import { u as useQuilttSession } from './useQuilttSession-client-
|
|
9
|
+
import { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
|
|
10
10
|
|
|
11
11
|
const useIdentifySession = (auth, setSession)=>{
|
|
12
12
|
const identifySession = useCallback(async (payload, callbacks)=>{
|
|
@@ -187,4 +187,4 @@ const useRevokeSession = (auth, session, setSession)=>{
|
|
|
187
187
|
});
|
|
188
188
|
};
|
|
189
189
|
|
|
190
|
-
export { QuilttAuthProvider as Q, useIdentifySession as a, useAuthenticateSession as b, useRevokeSession as c, useImportSession as u };
|
|
190
|
+
export { QuilttAuthProvider as Q, useIdentifySession as a, useAuthenticateSession as b, useRevokeSession as c, isDeepEqual as i, useImportSession as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,12 @@ type BaseQuilttButtonProps<T extends ElementType> = {
|
|
|
133
133
|
connectorId: string;
|
|
134
134
|
connectionId?: string;
|
|
135
135
|
institution?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Forces complete remount when connectionId changes.
|
|
138
|
+
* Useful as a fallback for ensuring clean state.
|
|
139
|
+
* @default false
|
|
140
|
+
*/
|
|
141
|
+
forceRemountOnConnectionChange?: boolean;
|
|
136
142
|
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
137
143
|
};
|
|
138
144
|
type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
|
|
@@ -140,18 +146,35 @@ type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
|
|
|
140
146
|
onHtmlLoad?: React.ReactEventHandler<HTMLElement>;
|
|
141
147
|
};
|
|
142
148
|
type QuilttButtonProps<T extends ElementType> = PropsWithChildren<BaseQuilttButtonProps<T> & QuilttCallbackProps>;
|
|
143
|
-
|
|
149
|
+
/**
|
|
150
|
+
* QuilttButton provides a clickable interface to open Quiltt connectors.
|
|
151
|
+
*
|
|
152
|
+
* When connectionId changes, the button will automatically update the existing
|
|
153
|
+
* connector instance with the new connection details. If you need to force a
|
|
154
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
155
|
+
*/
|
|
156
|
+
declare const QuilttButton: <T extends ElementType = "button">({ as, connectorId, connectionId, institution, forceRemountOnConnectionChange, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props }: QuilttButtonProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
|
|
144
157
|
|
|
145
158
|
type QuilttContainerProps<T extends ElementType> = PropsWithChildren<{
|
|
146
159
|
as?: T;
|
|
147
160
|
connectorId: string;
|
|
148
161
|
connectionId?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Forces complete remount when connectionId changes.
|
|
164
|
+
* Useful as a fallback for ensuring clean state.
|
|
165
|
+
* @default false
|
|
166
|
+
*/
|
|
167
|
+
forceRemountOnConnectionChange?: boolean;
|
|
149
168
|
} & ConnectorSDKCallbacks>;
|
|
150
169
|
/**
|
|
151
170
|
* QuilttContainer uses globally shared callbacks. It's recommended you only use
|
|
152
171
|
* one Container at a time.
|
|
172
|
+
*
|
|
173
|
+
* When connectionId changes, the container will automatically update the existing
|
|
174
|
+
* connector instance with the new connection details. If you need to force a
|
|
175
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
153
176
|
*/
|
|
154
|
-
declare const QuilttContainer: <T extends ElementType = "div">({ as, connectorId, connectionId, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
|
|
177
|
+
declare const QuilttContainer: <T extends ElementType = "div">({ as, connectorId, connectionId, forceRemountOnConnectionChange, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props }: QuilttContainerProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
|
|
155
178
|
|
|
156
179
|
export { QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, useAuthenticateSession, useEventListener, useIdentifySession, useImportSession, useIsomorphicLayoutEffect, useQuilttClient, useQuilttConnector, useQuilttSession, useQuilttSettings, useRevokeSession, useSession, useStorage };
|
|
157
180
|
export type { AuthenticateSession, IdentifySession, ImportSession, RevokeSession, SetSession, UseQuilttSession };
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
export * from '@quiltt/core';
|
|
2
2
|
export { u as useEventListener } from './useEventListener-client-DVM5xwKY.js';
|
|
3
3
|
export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-client-DeTHOKz1.js';
|
|
4
|
-
import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-
|
|
5
|
-
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-
|
|
4
|
+
import { Q as QuilttAuthProvider, i as isDeepEqual } from './QuilttAuthProvider-client-DLARZukU.js';
|
|
5
|
+
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-DLARZukU.js';
|
|
6
6
|
export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
|
|
7
|
-
import { u as useQuilttConnector } from './useQuilttConnector-client-
|
|
8
|
-
export { u as useQuilttSession } from './useQuilttSession-client-
|
|
7
|
+
import { u as useQuilttConnector } from './useQuilttConnector-client-BK7ybRZe.js';
|
|
8
|
+
export { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
|
|
9
9
|
export { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
|
|
10
10
|
export { u as useSession } from './useSession-client-CCAvnROP.js';
|
|
11
11
|
export { u as useStorage } from './useStorage-client-DHcq3Kuh.js';
|
|
12
12
|
import { jsx } from 'react/jsx-runtime';
|
|
13
13
|
import { Q as QuilttSettingsProvider } from './QuilttSettingsProvider-client-Va7uJ_dQ.js';
|
|
14
|
+
import { useRef, useEffect } from 'react';
|
|
14
15
|
|
|
15
16
|
const QuilttProvider = ({ clientId, token, children })=>{
|
|
16
17
|
return /*#__PURE__*/ jsx(QuilttSettingsProvider, {
|
|
@@ -22,7 +23,46 @@ const QuilttProvider = ({ clientId, token, children })=>{
|
|
|
22
23
|
});
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* QuilttButton provides a clickable interface to open Quiltt connectors.
|
|
28
|
+
*
|
|
29
|
+
* When connectionId changes, the button will automatically update the existing
|
|
30
|
+
* connector instance with the new connection details. If you need to force a
|
|
31
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
32
|
+
*/ const QuilttButton = ({ as, connectorId, connectionId, institution, forceRemountOnConnectionChange = false, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props })=>{
|
|
33
|
+
// Keep track of previous connectionId for change detection
|
|
34
|
+
const prevConnectionIdRef = useRef(connectionId);
|
|
35
|
+
const prevCallbacksRef = useRef({
|
|
36
|
+
onEvent,
|
|
37
|
+
onOpen,
|
|
38
|
+
onLoad,
|
|
39
|
+
onExit,
|
|
40
|
+
onExitSuccess,
|
|
41
|
+
onExitAbort,
|
|
42
|
+
onExitError
|
|
43
|
+
});
|
|
44
|
+
// Track if callbacks have changed to help with debugging
|
|
45
|
+
const currentCallbacks = {
|
|
46
|
+
onEvent,
|
|
47
|
+
onOpen,
|
|
48
|
+
onLoad,
|
|
49
|
+
onExit,
|
|
50
|
+
onExitSuccess,
|
|
51
|
+
onExitAbort,
|
|
52
|
+
onExitError
|
|
53
|
+
};
|
|
54
|
+
const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks);
|
|
55
|
+
useEffect(()=>{
|
|
56
|
+
prevCallbacksRef.current = currentCallbacks;
|
|
57
|
+
});
|
|
58
|
+
// Warning for potential callback reference issues
|
|
59
|
+
useEffect(()=>{
|
|
60
|
+
if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
|
|
61
|
+
console.warn('[Quiltt] Callback functions changed after initial render. ' + 'This may cause unexpected behavior. Consider memoizing callback functions ' + 'with useCallback to maintain stable references.');
|
|
62
|
+
}
|
|
63
|
+
}, [
|
|
64
|
+
callbacksChanged
|
|
65
|
+
]);
|
|
26
66
|
const { open } = useQuilttConnector(connectorId, {
|
|
27
67
|
connectionId,
|
|
28
68
|
institution,
|
|
@@ -35,6 +75,12 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
|
|
|
35
75
|
onExitAbort,
|
|
36
76
|
onExitError
|
|
37
77
|
});
|
|
78
|
+
// Update previous connectionId reference
|
|
79
|
+
useEffect(()=>{
|
|
80
|
+
prevConnectionIdRef.current = connectionId;
|
|
81
|
+
}, [
|
|
82
|
+
connectionId
|
|
83
|
+
]);
|
|
38
84
|
const Button = as || 'button';
|
|
39
85
|
const handleClick = (event)=>{
|
|
40
86
|
// Call the user's onClick handler first to allow for:
|
|
@@ -45,20 +91,58 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
|
|
|
45
91
|
// Then open the Quiltt connector
|
|
46
92
|
open();
|
|
47
93
|
};
|
|
94
|
+
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
95
|
+
const buttonKey = props.key ?? (forceRemountOnConnectionChange ? `${connectorId}-${connectionId || 'no-connection'}` : undefined);
|
|
48
96
|
return /*#__PURE__*/ jsx(Button, {
|
|
49
97
|
onClick: handleClick,
|
|
50
98
|
onLoad: onHtmlLoad,
|
|
51
99
|
"quiltt-connection": connectionId,
|
|
52
100
|
...props,
|
|
53
101
|
children: children
|
|
54
|
-
});
|
|
102
|
+
}, buttonKey);
|
|
55
103
|
};
|
|
56
104
|
|
|
57
105
|
/**
|
|
58
106
|
* QuilttContainer uses globally shared callbacks. It's recommended you only use
|
|
59
107
|
* one Container at a time.
|
|
60
|
-
|
|
108
|
+
*
|
|
109
|
+
* When connectionId changes, the container will automatically update the existing
|
|
110
|
+
* connector instance with the new connection details. If you need to force a
|
|
111
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
112
|
+
*/ const QuilttContainer = ({ as, connectorId, connectionId, forceRemountOnConnectionChange = false, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
|
|
113
|
+
// Keep track of previous connectionId for change detection
|
|
114
|
+
const prevConnectionIdRef = useRef(connectionId);
|
|
115
|
+
const prevCallbacksRef = useRef({
|
|
116
|
+
onEvent,
|
|
117
|
+
onLoad,
|
|
118
|
+
onExit,
|
|
119
|
+
onExitSuccess,
|
|
120
|
+
onExitAbort,
|
|
121
|
+
onExitError
|
|
122
|
+
});
|
|
123
|
+
// Track if callbacks have changed to help with debugging
|
|
124
|
+
const currentCallbacks = {
|
|
125
|
+
onEvent,
|
|
126
|
+
onLoad,
|
|
127
|
+
onExit,
|
|
128
|
+
onExitSuccess,
|
|
129
|
+
onExitAbort,
|
|
130
|
+
onExitError
|
|
131
|
+
};
|
|
132
|
+
const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks);
|
|
133
|
+
useEffect(()=>{
|
|
134
|
+
prevCallbacksRef.current = currentCallbacks;
|
|
135
|
+
});
|
|
136
|
+
// Warning for potential callback reference issues
|
|
137
|
+
useEffect(()=>{
|
|
138
|
+
if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
|
|
139
|
+
console.warn('[Quiltt] Callback functions changed after initial render. ' + 'This may cause unexpected behavior. Consider memoizing callback functions ' + 'with useCallback to maintain stable references.');
|
|
140
|
+
}
|
|
141
|
+
}, [
|
|
142
|
+
callbacksChanged
|
|
143
|
+
]);
|
|
61
144
|
useQuilttConnector(connectorId, {
|
|
145
|
+
connectionId,
|
|
62
146
|
nonce: props?.nonce,
|
|
63
147
|
onEvent,
|
|
64
148
|
onLoad,
|
|
@@ -67,13 +151,21 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
|
|
|
67
151
|
onExitAbort,
|
|
68
152
|
onExitError
|
|
69
153
|
});
|
|
154
|
+
// Update previous connectionId reference
|
|
155
|
+
useEffect(()=>{
|
|
156
|
+
prevConnectionIdRef.current = connectionId;
|
|
157
|
+
}, [
|
|
158
|
+
connectionId
|
|
159
|
+
]);
|
|
70
160
|
const Container = as || 'div';
|
|
161
|
+
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
162
|
+
const containerKey = props.key ?? (forceRemountOnConnectionChange ? `${connectorId}-${connectionId || 'no-connection'}` : undefined);
|
|
71
163
|
return /*#__PURE__*/ jsx(Container, {
|
|
72
164
|
"quiltt-container": connectorId,
|
|
73
165
|
"quiltt-connection": connectionId,
|
|
74
166
|
...props,
|
|
75
167
|
children: children
|
|
76
|
-
});
|
|
168
|
+
}, containerKey);
|
|
77
169
|
};
|
|
78
170
|
|
|
79
171
|
export { QuilttAuthProvider, QuilttButton, QuilttContainer, QuilttProvider, QuilttSettingsProvider, useQuilttConnector };
|
package/dist/{useQuilttConnector-client-BB7kw8vs.js → useQuilttConnector-client-BK7ybRZe.js}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { cdnBase } from '@quiltt/core';
|
|
4
|
-
import { u as useQuilttSession } from './useQuilttSession-client-
|
|
4
|
+
import { u as useQuilttSession } from './useQuilttSession-client-Ddb55W0n.js';
|
|
5
5
|
import { u as useScript } from './useScript-client-JCgaTW9n.js';
|
|
6
6
|
|
|
7
|
-
var version = "4.
|
|
7
|
+
var version = "4.2.0";
|
|
8
8
|
|
|
9
9
|
const useQuilttConnector = (connectorId, options)=>{
|
|
10
10
|
const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`, {
|
|
@@ -15,33 +15,60 @@ const useQuilttConnector = (connectorId, options)=>{
|
|
|
15
15
|
const session = useQuilttSessionReturn?.session || null;
|
|
16
16
|
const [connector, setConnector] = useState();
|
|
17
17
|
const [isOpening, setIsOpening] = useState(false);
|
|
18
|
+
// Keep track of the previous connectionId to detect changes
|
|
19
|
+
const prevConnectionIdRef = useRef(options?.connectionId);
|
|
20
|
+
const prevConnectorIdRef = useRef(connectorId);
|
|
21
|
+
const connectorCreatedRef = useRef(false);
|
|
18
22
|
// Set Session
|
|
19
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
|
|
20
23
|
useEffect(()=>{
|
|
21
24
|
if (typeof Quiltt === 'undefined') return;
|
|
25
|
+
console.debug('[Quiltt] script status: ', status);
|
|
22
26
|
Quiltt.authenticate(session?.token);
|
|
23
27
|
}, [
|
|
24
28
|
status,
|
|
25
29
|
session?.token
|
|
26
30
|
]);
|
|
27
31
|
// Set Connector
|
|
28
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
|
|
29
32
|
useEffect(()=>{
|
|
30
33
|
if (typeof Quiltt === 'undefined' || !connectorId) return;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
console.debug('[Quiltt] script status: ', status);
|
|
35
|
+
const currentConnectionId = options?.connectionId;
|
|
36
|
+
const currentInstitution = options?.institution;
|
|
37
|
+
// Check if this is a connectionId change on the same connector
|
|
38
|
+
const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId;
|
|
39
|
+
const connectorIdChanged = prevConnectorIdRef.current !== connectorId;
|
|
40
|
+
// If only connectionId changed (not the connectorId), the core SDK should handle this
|
|
41
|
+
// via the updated Handler.updateOptions method, so we don't need to recreate the connector
|
|
42
|
+
if (connectionIdChanged && !connectorIdChanged && connectorCreatedRef.current) {
|
|
43
|
+
// The SDK will automatically update the existing handler with new connectionId
|
|
44
|
+
// via the DocumentObserver -> Engine.onChange -> Handler.updateOptions flow
|
|
45
|
+
console.debug('[Quiltt] connectionId changed, SDK will handle update automatically');
|
|
46
|
+
// Update our refs
|
|
47
|
+
prevConnectionIdRef.current = currentConnectionId;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Only create new connector if we haven't created one yet or if connectorId changed
|
|
51
|
+
if (!connectorCreatedRef.current || connectorIdChanged) {
|
|
52
|
+
// Create new connector (initial mount or connectorId changed)
|
|
53
|
+
if (currentConnectionId) {
|
|
54
|
+
setConnector(Quiltt.reconnect(connectorId, {
|
|
55
|
+
connectionId: currentConnectionId
|
|
56
|
+
}));
|
|
57
|
+
} else {
|
|
58
|
+
setConnector(Quiltt.connect(connectorId, {
|
|
59
|
+
institution: currentInstitution
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
connectorCreatedRef.current = true;
|
|
39
63
|
}
|
|
64
|
+
// Update refs
|
|
65
|
+
prevConnectionIdRef.current = currentConnectionId;
|
|
66
|
+
prevConnectorIdRef.current = connectorId;
|
|
40
67
|
}, [
|
|
41
|
-
status,
|
|
42
68
|
connectorId,
|
|
43
69
|
options?.connectionId,
|
|
44
|
-
options?.institution
|
|
70
|
+
options?.institution,
|
|
71
|
+
status
|
|
45
72
|
]);
|
|
46
73
|
// onEvent
|
|
47
74
|
useEffect(()=>{
|
|
@@ -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-client-
|
|
4
|
+
import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-DLARZukU.js';
|
|
5
5
|
import { u as useQuilttSettings } from './useQuilttSettings-client-BOCBjFXe.js';
|
|
6
6
|
import { u as useSession } from './useSession-client-CCAvnROP.js';
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/react",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
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.12.4",
|
|
38
38
|
"use-debounce": "^10.0.4",
|
|
39
|
-
"@quiltt/core": "4.
|
|
39
|
+
"@quiltt/core": "4.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "1.9.4",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
1
2
|
import type { ElementType, MouseEvent, PropsWithChildren } from 'react'
|
|
2
3
|
|
|
3
4
|
import type { ConnectorSDKCallbacks } from '@quiltt/core'
|
|
4
5
|
|
|
5
6
|
import { useQuilttConnector } from '@/hooks/useQuilttConnector'
|
|
6
7
|
import type { PropsOf } from '@/types'
|
|
8
|
+
import { isDeepEqual } from '@/utils/isDeepEqual'
|
|
7
9
|
|
|
8
10
|
// Base button props without callback-specific properties
|
|
9
11
|
type BaseQuilttButtonProps<T extends ElementType> = {
|
|
@@ -11,6 +13,14 @@ type BaseQuilttButtonProps<T extends ElementType> = {
|
|
|
11
13
|
connectorId: string
|
|
12
14
|
connectionId?: string // For Reconnect Mode
|
|
13
15
|
institution?: string // For Connect Mode
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Forces complete remount when connectionId changes.
|
|
19
|
+
* Useful as a fallback for ensuring clean state.
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
forceRemountOnConnectionChange?: boolean
|
|
23
|
+
|
|
14
24
|
// Override the native onClick handler
|
|
15
25
|
onClick?: (event: MouseEvent<HTMLElement>) => void
|
|
16
26
|
}
|
|
@@ -27,11 +37,19 @@ type QuilttButtonProps<T extends ElementType> = PropsWithChildren<
|
|
|
27
37
|
BaseQuilttButtonProps<T> & QuilttCallbackProps
|
|
28
38
|
>
|
|
29
39
|
|
|
40
|
+
/**
|
|
41
|
+
* QuilttButton provides a clickable interface to open Quiltt connectors.
|
|
42
|
+
*
|
|
43
|
+
* When connectionId changes, the button will automatically update the existing
|
|
44
|
+
* connector instance with the new connection details. If you need to force a
|
|
45
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
46
|
+
*/
|
|
30
47
|
export const QuilttButton = <T extends ElementType = 'button'>({
|
|
31
48
|
as,
|
|
32
49
|
connectorId,
|
|
33
50
|
connectionId,
|
|
34
51
|
institution,
|
|
52
|
+
forceRemountOnConnectionChange = false,
|
|
35
53
|
onEvent,
|
|
36
54
|
onOpen,
|
|
37
55
|
onLoad,
|
|
@@ -44,6 +62,46 @@ export const QuilttButton = <T extends ElementType = 'button'>({
|
|
|
44
62
|
children,
|
|
45
63
|
...props
|
|
46
64
|
}: QuilttButtonProps<T> & PropsOf<T>) => {
|
|
65
|
+
// Keep track of previous connectionId for change detection
|
|
66
|
+
const prevConnectionIdRef = useRef<string | undefined>(connectionId)
|
|
67
|
+
const prevCallbacksRef = useRef({
|
|
68
|
+
onEvent,
|
|
69
|
+
onOpen,
|
|
70
|
+
onLoad,
|
|
71
|
+
onExit,
|
|
72
|
+
onExitSuccess,
|
|
73
|
+
onExitAbort,
|
|
74
|
+
onExitError,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Track if callbacks have changed to help with debugging
|
|
78
|
+
const currentCallbacks = {
|
|
79
|
+
onEvent,
|
|
80
|
+
onOpen,
|
|
81
|
+
onLoad,
|
|
82
|
+
onExit,
|
|
83
|
+
onExitSuccess,
|
|
84
|
+
onExitAbort,
|
|
85
|
+
onExitError,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks)
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
prevCallbacksRef.current = currentCallbacks
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Warning for potential callback reference issues
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
|
|
97
|
+
console.warn(
|
|
98
|
+
'[Quiltt] Callback functions changed after initial render. ' +
|
|
99
|
+
'This may cause unexpected behavior. Consider memoizing callback functions ' +
|
|
100
|
+
'with useCallback to maintain stable references.'
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}, [callbacksChanged])
|
|
104
|
+
|
|
47
105
|
const { open } = useQuilttConnector(connectorId, {
|
|
48
106
|
connectionId,
|
|
49
107
|
institution,
|
|
@@ -57,6 +115,11 @@ export const QuilttButton = <T extends ElementType = 'button'>({
|
|
|
57
115
|
onExitError,
|
|
58
116
|
})
|
|
59
117
|
|
|
118
|
+
// Update previous connectionId reference
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
prevConnectionIdRef.current = connectionId
|
|
121
|
+
}, [connectionId])
|
|
122
|
+
|
|
60
123
|
const Button = as || 'button'
|
|
61
124
|
|
|
62
125
|
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
@@ -70,8 +133,21 @@ export const QuilttButton = <T extends ElementType = 'button'>({
|
|
|
70
133
|
open()
|
|
71
134
|
}
|
|
72
135
|
|
|
136
|
+
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
137
|
+
const buttonKey =
|
|
138
|
+
props.key ??
|
|
139
|
+
(forceRemountOnConnectionChange
|
|
140
|
+
? `${connectorId}-${connectionId || 'no-connection'}`
|
|
141
|
+
: undefined)
|
|
142
|
+
|
|
73
143
|
return (
|
|
74
|
-
<Button
|
|
144
|
+
<Button
|
|
145
|
+
key={buttonKey}
|
|
146
|
+
onClick={handleClick}
|
|
147
|
+
onLoad={onHtmlLoad}
|
|
148
|
+
quiltt-connection={connectionId}
|
|
149
|
+
{...props}
|
|
150
|
+
>
|
|
75
151
|
{children}
|
|
76
152
|
</Button>
|
|
77
153
|
)
|
|
@@ -1,26 +1,40 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
1
2
|
import type { ElementType, PropsWithChildren } from 'react'
|
|
2
3
|
|
|
3
4
|
import type { ConnectorSDKCallbacks } from '@quiltt/core'
|
|
4
5
|
|
|
5
6
|
import { useQuilttConnector } from '@/hooks/useQuilttConnector'
|
|
6
7
|
import type { PropsOf } from '@/types'
|
|
8
|
+
import { isDeepEqual } from '@/utils/isDeepEqual'
|
|
7
9
|
|
|
8
10
|
type QuilttContainerProps<T extends ElementType> = PropsWithChildren<
|
|
9
11
|
{
|
|
10
12
|
as?: T
|
|
11
13
|
connectorId: string
|
|
12
14
|
connectionId?: string // For Reconnect Mode
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Forces complete remount when connectionId changes.
|
|
18
|
+
* Useful as a fallback for ensuring clean state.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
forceRemountOnConnectionChange?: boolean
|
|
13
22
|
} & ConnectorSDKCallbacks
|
|
14
23
|
>
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
26
|
* QuilttContainer uses globally shared callbacks. It's recommended you only use
|
|
18
27
|
* one Container at a time.
|
|
28
|
+
*
|
|
29
|
+
* When connectionId changes, the container will automatically update the existing
|
|
30
|
+
* connector instance with the new connection details. If you need to force a
|
|
31
|
+
* complete remount instead, set forceRemountOnConnectionChange to true.
|
|
19
32
|
*/
|
|
20
33
|
export const QuilttContainer = <T extends ElementType = 'div'>({
|
|
21
34
|
as,
|
|
22
35
|
connectorId,
|
|
23
36
|
connectionId,
|
|
37
|
+
forceRemountOnConnectionChange = false,
|
|
24
38
|
onEvent,
|
|
25
39
|
onLoad,
|
|
26
40
|
onExit,
|
|
@@ -30,7 +44,46 @@ export const QuilttContainer = <T extends ElementType = 'div'>({
|
|
|
30
44
|
children,
|
|
31
45
|
...props
|
|
32
46
|
}: QuilttContainerProps<T> & PropsOf<T>) => {
|
|
47
|
+
// Keep track of previous connectionId for change detection
|
|
48
|
+
const prevConnectionIdRef = useRef<string | undefined>(connectionId)
|
|
49
|
+
const prevCallbacksRef = useRef({
|
|
50
|
+
onEvent,
|
|
51
|
+
onLoad,
|
|
52
|
+
onExit,
|
|
53
|
+
onExitSuccess,
|
|
54
|
+
onExitAbort,
|
|
55
|
+
onExitError,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Track if callbacks have changed to help with debugging
|
|
59
|
+
const currentCallbacks = {
|
|
60
|
+
onEvent,
|
|
61
|
+
onLoad,
|
|
62
|
+
onExit,
|
|
63
|
+
onExitSuccess,
|
|
64
|
+
onExitAbort,
|
|
65
|
+
onExitError,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const callbacksChanged = !isDeepEqual(prevCallbacksRef.current, currentCallbacks)
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
prevCallbacksRef.current = currentCallbacks
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Warning for potential callback reference issues
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (callbacksChanged && prevConnectionIdRef.current !== undefined) {
|
|
77
|
+
console.warn(
|
|
78
|
+
'[Quiltt] Callback functions changed after initial render. ' +
|
|
79
|
+
'This may cause unexpected behavior. Consider memoizing callback functions ' +
|
|
80
|
+
'with useCallback to maintain stable references.'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}, [callbacksChanged])
|
|
84
|
+
|
|
33
85
|
useQuilttConnector(connectorId, {
|
|
86
|
+
connectionId,
|
|
34
87
|
nonce: props?.nonce, // Pass nonce for script loading if needed
|
|
35
88
|
onEvent,
|
|
36
89
|
onLoad,
|
|
@@ -40,10 +93,27 @@ export const QuilttContainer = <T extends ElementType = 'div'>({
|
|
|
40
93
|
onExitError,
|
|
41
94
|
})
|
|
42
95
|
|
|
96
|
+
// Update previous connectionId reference
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
prevConnectionIdRef.current = connectionId
|
|
99
|
+
}, [connectionId])
|
|
100
|
+
|
|
43
101
|
const Container = as || 'div'
|
|
44
102
|
|
|
103
|
+
// Generate key for forced remounting if enabled, but respect user-provided key
|
|
104
|
+
const containerKey =
|
|
105
|
+
props.key ??
|
|
106
|
+
(forceRemountOnConnectionChange
|
|
107
|
+
? `${connectorId}-${connectionId || 'no-connection'}`
|
|
108
|
+
: undefined)
|
|
109
|
+
|
|
45
110
|
return (
|
|
46
|
-
<Container
|
|
111
|
+
<Container
|
|
112
|
+
key={containerKey}
|
|
113
|
+
quiltt-container={connectorId}
|
|
114
|
+
quiltt-connection={connectionId}
|
|
115
|
+
{...props}
|
|
116
|
+
>
|
|
47
117
|
{children}
|
|
48
118
|
</Container>
|
|
49
119
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
4
4
|
|
|
5
5
|
import { cdnBase } from '@quiltt/core'
|
|
6
6
|
import type {
|
|
@@ -30,25 +30,59 @@ export const useQuilttConnector = (
|
|
|
30
30
|
const [connector, setConnector] = useState<ConnectorSDKConnector>()
|
|
31
31
|
const [isOpening, setIsOpening] = useState<boolean>(false)
|
|
32
32
|
|
|
33
|
+
// Keep track of the previous connectionId to detect changes
|
|
34
|
+
const prevConnectionIdRef = useRef<string | undefined>(options?.connectionId)
|
|
35
|
+
const prevConnectorIdRef = useRef<string | undefined>(connectorId)
|
|
36
|
+
const connectorCreatedRef = useRef<boolean>(false)
|
|
37
|
+
|
|
33
38
|
// Set Session
|
|
34
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
|
|
35
39
|
useEffect(() => {
|
|
36
40
|
if (typeof Quiltt === 'undefined') return
|
|
41
|
+
console.debug('[Quiltt] script status: ', status)
|
|
37
42
|
|
|
38
43
|
Quiltt.authenticate(session?.token)
|
|
39
44
|
}, [status, session?.token])
|
|
40
45
|
|
|
41
46
|
// Set Connector
|
|
42
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We also need to update on status change
|
|
43
47
|
useEffect(() => {
|
|
44
48
|
if (typeof Quiltt === 'undefined' || !connectorId) return
|
|
49
|
+
console.debug('[Quiltt] script status: ', status)
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
const currentConnectionId = options?.connectionId
|
|
52
|
+
const currentInstitution = options?.institution
|
|
53
|
+
|
|
54
|
+
// Check if this is a connectionId change on the same connector
|
|
55
|
+
const connectionIdChanged = prevConnectionIdRef.current !== currentConnectionId
|
|
56
|
+
const connectorIdChanged = prevConnectorIdRef.current !== connectorId
|
|
57
|
+
|
|
58
|
+
// If only connectionId changed (not the connectorId), the core SDK should handle this
|
|
59
|
+
// via the updated Handler.updateOptions method, so we don't need to recreate the connector
|
|
60
|
+
if (connectionIdChanged && !connectorIdChanged && connectorCreatedRef.current) {
|
|
61
|
+
// The SDK will automatically update the existing handler with new connectionId
|
|
62
|
+
// via the DocumentObserver -> Engine.onChange -> Handler.updateOptions flow
|
|
63
|
+
console.debug('[Quiltt] connectionId changed, SDK will handle update automatically')
|
|
64
|
+
|
|
65
|
+
// Update our refs
|
|
66
|
+
prevConnectionIdRef.current = currentConnectionId
|
|
67
|
+
return
|
|
50
68
|
}
|
|
51
|
-
|
|
69
|
+
|
|
70
|
+
// Only create new connector if we haven't created one yet or if connectorId changed
|
|
71
|
+
if (!connectorCreatedRef.current || connectorIdChanged) {
|
|
72
|
+
// Create new connector (initial mount or connectorId changed)
|
|
73
|
+
if (currentConnectionId) {
|
|
74
|
+
setConnector(Quiltt.reconnect(connectorId, { connectionId: currentConnectionId }))
|
|
75
|
+
} else {
|
|
76
|
+
setConnector(Quiltt.connect(connectorId, { institution: currentInstitution }))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
connectorCreatedRef.current = true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update refs
|
|
83
|
+
prevConnectionIdRef.current = currentConnectionId
|
|
84
|
+
prevConnectorIdRef.current = connectorId
|
|
85
|
+
}, [connectorId, options?.connectionId, options?.institution, status])
|
|
52
86
|
|
|
53
87
|
// onEvent
|
|
54
88
|
useEffect(() => {
|