@quiltt/capacitor 5.1.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/LICENSE.md +9 -0
- package/QuilttConnector.podspec +17 -0
- package/README.md +214 -0
- package/android/build.gradle +59 -0
- package/android/proguard-rules.pro +11 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/app/quiltt/capacitor/QuilttConnectorPlugin.kt +83 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +10 -0
- package/dist/react.cjs +296 -0
- package/dist/react.d.ts +109 -0
- package/dist/react.js +288 -0
- package/dist/vue.cjs +19 -0
- package/dist/vue.d.ts +87 -0
- package/dist/vue.js +11 -0
- package/dist/web-BgcuNl8a.cjs +42 -0
- package/dist/web-CUWsqcUV.js +42 -0
- package/ios/Sources/QuilttConnectorPlugin/QuilttConnectorPlugin.swift +98 -0
- package/package.json +121 -0
- package/src/components/QuilttConnector.tsx +349 -0
- package/src/components/index.ts +1 -0
- package/src/definitions.ts +81 -0
- package/src/index.ts +31 -0
- package/src/plugin.ts +11 -0
- package/src/react.ts +33 -0
- package/src/vue.ts +42 -0
- package/src/web.ts +44 -0
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var react = require('@quiltt/react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var react$1 = require('react');
|
|
6
|
+
var core = require('@capacitor/core');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native Capacitor plugin for deep link handling and URL opening
|
|
10
|
+
* Used internally by QuilttConnector component for OAuth flows
|
|
11
|
+
*/ const QuilttConnector$1 = core.registerPlugin('QuilttConnector', {
|
|
12
|
+
web: ()=>Promise.resolve().then(function () { return require('./web-BgcuNl8a.cjs'); }).then((m)=>new m.QuilttConnectorWeb())
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const trustedQuilttHostSuffixes = [
|
|
16
|
+
'quiltt.io',
|
|
17
|
+
'quiltt.dev',
|
|
18
|
+
'quiltt.app'
|
|
19
|
+
];
|
|
20
|
+
const isTrustedQuilttOrigin = (origin)=>{
|
|
21
|
+
try {
|
|
22
|
+
const originUrl = new URL(origin);
|
|
23
|
+
if (originUrl.protocol !== 'https:') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const hostname = originUrl.hostname.toLowerCase();
|
|
27
|
+
return trustedQuilttHostSuffixes.some((suffix)=>hostname === suffix || hostname.endsWith(`.${suffix}`));
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const decodeIfEncoded = (value)=>{
|
|
33
|
+
try {
|
|
34
|
+
const decoded = decodeURIComponent(value);
|
|
35
|
+
return decoded === value ? value : decoded;
|
|
36
|
+
} catch {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const normalizeUrlValue = (value)=>decodeIfEncoded(value.trim());
|
|
41
|
+
/**
|
|
42
|
+
* QuilttConnector component for Capacitor apps
|
|
43
|
+
* Embeds the Quiltt Connector in an iframe and handles OAuth flows via native plugins
|
|
44
|
+
*/ const QuilttConnector = /*#__PURE__*/ react$1.forwardRef(({ connectorId, connectionId, institution, appLauncherUrl, style, className, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError }, ref)=>{
|
|
45
|
+
const iframeRef = react$1.useRef(null);
|
|
46
|
+
const { session } = react.useQuilttSession();
|
|
47
|
+
const [isLoaded, setIsLoaded] = react$1.useState(false);
|
|
48
|
+
const [loadError, setLoadError] = react$1.useState(null);
|
|
49
|
+
// Connector origin for secure postMessage targeting
|
|
50
|
+
const connectorOrigin = react$1.useMemo(()=>`https://${connectorId}.quiltt.app`, [
|
|
51
|
+
connectorId
|
|
52
|
+
]);
|
|
53
|
+
// Build connector URL
|
|
54
|
+
const connectorUrl = react$1.useMemo(()=>{
|
|
55
|
+
const url = new URL(connectorOrigin);
|
|
56
|
+
if (session?.token) {
|
|
57
|
+
url.searchParams.set('token', session.token);
|
|
58
|
+
}
|
|
59
|
+
if (connectionId) {
|
|
60
|
+
url.searchParams.set('connectionId', connectionId);
|
|
61
|
+
}
|
|
62
|
+
if (institution) {
|
|
63
|
+
url.searchParams.set('institution', institution);
|
|
64
|
+
}
|
|
65
|
+
if (appLauncherUrl) {
|
|
66
|
+
url.searchParams.set('app_launcher_url', normalizeUrlValue(appLauncherUrl));
|
|
67
|
+
}
|
|
68
|
+
if (typeof window !== 'undefined') {
|
|
69
|
+
url.searchParams.set('embed_location', window.location.href);
|
|
70
|
+
}
|
|
71
|
+
// Set mode for inline iframe embedding
|
|
72
|
+
url.searchParams.set('mode', 'INLINE');
|
|
73
|
+
return url.toString();
|
|
74
|
+
}, [
|
|
75
|
+
connectorOrigin,
|
|
76
|
+
session?.token,
|
|
77
|
+
connectionId,
|
|
78
|
+
institution,
|
|
79
|
+
appLauncherUrl
|
|
80
|
+
]);
|
|
81
|
+
react$1.useEffect(()=>{
|
|
82
|
+
setIsLoaded(false);
|
|
83
|
+
setLoadError(null);
|
|
84
|
+
const abortController = new AbortController();
|
|
85
|
+
const runPreflight = async ()=>{
|
|
86
|
+
try {
|
|
87
|
+
await fetch(connectorUrl, {
|
|
88
|
+
method: 'GET',
|
|
89
|
+
mode: 'no-cors',
|
|
90
|
+
credentials: 'omit',
|
|
91
|
+
signal: abortController.signal
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
setLoadError('Unable to reach Quiltt Connector. Check network and connector settings.');
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
void runPreflight();
|
|
98
|
+
return ()=>{
|
|
99
|
+
abortController.abort();
|
|
100
|
+
};
|
|
101
|
+
}, [
|
|
102
|
+
connectorUrl
|
|
103
|
+
]);
|
|
104
|
+
react$1.useEffect(()=>{
|
|
105
|
+
if (isLoaded || loadError) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const timeoutId = window.setTimeout(()=>{
|
|
109
|
+
setLoadError('Connector took too long to load. Please retry.');
|
|
110
|
+
}, 15000);
|
|
111
|
+
return ()=>{
|
|
112
|
+
window.clearTimeout(timeoutId);
|
|
113
|
+
};
|
|
114
|
+
}, [
|
|
115
|
+
isLoaded,
|
|
116
|
+
loadError
|
|
117
|
+
]);
|
|
118
|
+
const postOAuthCallbackToIframe = react$1.useCallback((callbackUrl)=>{
|
|
119
|
+
if (!iframeRef.current?.contentWindow) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const normalizedCallbackUrl = normalizeUrlValue(callbackUrl);
|
|
123
|
+
try {
|
|
124
|
+
const callback = new URL(normalizedCallbackUrl);
|
|
125
|
+
const params = {};
|
|
126
|
+
callback.searchParams.forEach((value, key)=>{
|
|
127
|
+
params[key] = value;
|
|
128
|
+
});
|
|
129
|
+
iframeRef.current.contentWindow.postMessage({
|
|
130
|
+
source: 'quiltt',
|
|
131
|
+
type: 'OAuthCallback',
|
|
132
|
+
data: {
|
|
133
|
+
url: normalizedCallbackUrl,
|
|
134
|
+
params
|
|
135
|
+
}
|
|
136
|
+
}, connectorOrigin);
|
|
137
|
+
} catch {
|
|
138
|
+
iframeRef.current.contentWindow.postMessage({
|
|
139
|
+
source: 'quiltt',
|
|
140
|
+
type: 'OAuthCallback',
|
|
141
|
+
data: {
|
|
142
|
+
url: normalizedCallbackUrl,
|
|
143
|
+
params: {}
|
|
144
|
+
}
|
|
145
|
+
}, connectorOrigin);
|
|
146
|
+
}
|
|
147
|
+
}, [
|
|
148
|
+
connectorOrigin
|
|
149
|
+
]);
|
|
150
|
+
// Handle messages from the iframe
|
|
151
|
+
// The platform MessageBus sends: { source: 'quiltt', type: 'Load'|'ExitSuccess'|..., ...metadata }
|
|
152
|
+
const handleMessage = react$1.useCallback((event)=>{
|
|
153
|
+
// Validate origin
|
|
154
|
+
if (!isTrustedQuilttOrigin(event.origin)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const data = event.data || {};
|
|
158
|
+
// Validate message is from Quiltt MessageBus
|
|
159
|
+
if (data.source !== 'quiltt' || !data.type) return;
|
|
160
|
+
const { type, connectionId: msgConnectionId, profileId, connectorSession, url } = data;
|
|
161
|
+
// Build metadata from message fields
|
|
162
|
+
const metadata = {
|
|
163
|
+
connectorId,
|
|
164
|
+
...profileId && {
|
|
165
|
+
profileId
|
|
166
|
+
},
|
|
167
|
+
...msgConnectionId && {
|
|
168
|
+
connectionId: msgConnectionId
|
|
169
|
+
},
|
|
170
|
+
...connectorSession && {
|
|
171
|
+
connectorSession
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
switch(type){
|
|
175
|
+
case 'Load':
|
|
176
|
+
setIsLoaded(true);
|
|
177
|
+
setLoadError(null);
|
|
178
|
+
onEvent?.(react.ConnectorSDKEventType.Load, metadata);
|
|
179
|
+
onLoad?.(metadata);
|
|
180
|
+
break;
|
|
181
|
+
case 'ExitSuccess':
|
|
182
|
+
onEvent?.(react.ConnectorSDKEventType.ExitSuccess, metadata);
|
|
183
|
+
onExit?.(react.ConnectorSDKEventType.ExitSuccess, metadata);
|
|
184
|
+
onExitSuccess?.(metadata);
|
|
185
|
+
break;
|
|
186
|
+
case 'ExitAbort':
|
|
187
|
+
onEvent?.(react.ConnectorSDKEventType.ExitAbort, metadata);
|
|
188
|
+
onExit?.(react.ConnectorSDKEventType.ExitAbort, metadata);
|
|
189
|
+
onExitAbort?.(metadata);
|
|
190
|
+
break;
|
|
191
|
+
case 'ExitError':
|
|
192
|
+
onEvent?.(react.ConnectorSDKEventType.ExitError, metadata);
|
|
193
|
+
onExit?.(react.ConnectorSDKEventType.ExitError, metadata);
|
|
194
|
+
onExitError?.(metadata);
|
|
195
|
+
break;
|
|
196
|
+
case 'Navigate':
|
|
197
|
+
// OAuth URL - open in system browser
|
|
198
|
+
if (url) {
|
|
199
|
+
QuilttConnector$1.openUrl({
|
|
200
|
+
url
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}, [
|
|
206
|
+
connectorId,
|
|
207
|
+
onEvent,
|
|
208
|
+
onLoad,
|
|
209
|
+
onExit,
|
|
210
|
+
onExitSuccess,
|
|
211
|
+
onExitAbort,
|
|
212
|
+
onExitError
|
|
213
|
+
]);
|
|
214
|
+
// Set up message listener
|
|
215
|
+
react$1.useEffect(()=>{
|
|
216
|
+
window.addEventListener('message', handleMessage);
|
|
217
|
+
return ()=>window.removeEventListener('message', handleMessage);
|
|
218
|
+
}, [
|
|
219
|
+
handleMessage
|
|
220
|
+
]);
|
|
221
|
+
// Listen for OAuth callbacks via deep links
|
|
222
|
+
react$1.useEffect(()=>{
|
|
223
|
+
const listener = QuilttConnector$1.addListener('deepLink', (event)=>{
|
|
224
|
+
if (event.url) {
|
|
225
|
+
postOAuthCallbackToIframe(event.url);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
// Check if app was launched with a URL
|
|
229
|
+
QuilttConnector$1.getLaunchUrl().then((result)=>{
|
|
230
|
+
if (result?.url) {
|
|
231
|
+
postOAuthCallbackToIframe(result.url);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return ()=>{
|
|
235
|
+
listener.then((l)=>l.remove());
|
|
236
|
+
};
|
|
237
|
+
}, [
|
|
238
|
+
postOAuthCallbackToIframe
|
|
239
|
+
]);
|
|
240
|
+
// Expose method to handle OAuth callbacks from parent component
|
|
241
|
+
react$1.useImperativeHandle(ref, ()=>({
|
|
242
|
+
handleOAuthCallback: (callbackUrl)=>{
|
|
243
|
+
postOAuthCallbackToIframe(callbackUrl);
|
|
244
|
+
}
|
|
245
|
+
}), [
|
|
246
|
+
postOAuthCallbackToIframe
|
|
247
|
+
]);
|
|
248
|
+
return /*#__PURE__*/ jsxRuntime.jsxs("div", {
|
|
249
|
+
className: className,
|
|
250
|
+
style: {
|
|
251
|
+
width: '100%',
|
|
252
|
+
height: '100%',
|
|
253
|
+
position: 'relative',
|
|
254
|
+
...style
|
|
255
|
+
},
|
|
256
|
+
children: [
|
|
257
|
+
/*#__PURE__*/ jsxRuntime.jsx("iframe", {
|
|
258
|
+
ref: iframeRef,
|
|
259
|
+
src: connectorUrl,
|
|
260
|
+
title: "Quiltt Connector",
|
|
261
|
+
allow: "publickey-credentials-get *",
|
|
262
|
+
style: {
|
|
263
|
+
border: 'none',
|
|
264
|
+
width: '100%',
|
|
265
|
+
height: '100%'
|
|
266
|
+
},
|
|
267
|
+
onError: ()=>{
|
|
268
|
+
setLoadError('Unable to load Quiltt Connector iframe.');
|
|
269
|
+
}
|
|
270
|
+
}),
|
|
271
|
+
loadError ? /*#__PURE__*/ jsxRuntime.jsx("div", {
|
|
272
|
+
style: {
|
|
273
|
+
position: 'absolute',
|
|
274
|
+
inset: 0,
|
|
275
|
+
display: 'flex',
|
|
276
|
+
alignItems: 'center',
|
|
277
|
+
justifyContent: 'center',
|
|
278
|
+
padding: '16px',
|
|
279
|
+
textAlign: 'center',
|
|
280
|
+
backgroundColor: '#fff'
|
|
281
|
+
},
|
|
282
|
+
children: loadError
|
|
283
|
+
}) : null
|
|
284
|
+
]
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
QuilttConnector.displayName = 'QuilttConnector';
|
|
288
|
+
|
|
289
|
+
exports.QuilttConnector = QuilttConnector;
|
|
290
|
+
exports.QuilttConnectorPlugin = QuilttConnector$1;
|
|
291
|
+
Object.keys(react).forEach(function (k) {
|
|
292
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
293
|
+
enumerable: true,
|
|
294
|
+
get: function () { return react[k]; }
|
|
295
|
+
});
|
|
296
|
+
});
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ConnectorSDKCallbacks } from '@quiltt/react';
|
|
2
|
+
export * from '@quiltt/react';
|
|
3
|
+
import * as react$1 from 'react';
|
|
4
|
+
import { PluginListenerHandle } from '@capacitor/core';
|
|
5
|
+
|
|
6
|
+
type QuilttConnectorHandle = {
|
|
7
|
+
handleOAuthCallback: (url: string) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* QuilttConnector component for Capacitor apps
|
|
11
|
+
* Embeds the Quiltt Connector in an iframe and handles OAuth flows via native plugins
|
|
12
|
+
*/
|
|
13
|
+
declare const QuilttConnector$1: react$1.ForwardRefExoticComponent<{
|
|
14
|
+
connectorId: string;
|
|
15
|
+
connectionId?: string;
|
|
16
|
+
institution?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The app launcher URL for mobile OAuth flows.
|
|
19
|
+
* This URL should be a Universal Link (iOS) or App Link (Android) that redirects back to your app.
|
|
20
|
+
*/
|
|
21
|
+
appLauncherUrl?: string;
|
|
22
|
+
style?: React.CSSProperties;
|
|
23
|
+
className?: string;
|
|
24
|
+
} & ConnectorSDKCallbacks & react$1.RefAttributes<QuilttConnectorHandle>>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Options for opening a URL in the system browser
|
|
28
|
+
*/
|
|
29
|
+
interface OpenUrlOptions {
|
|
30
|
+
/**
|
|
31
|
+
* The URL to open in the system browser
|
|
32
|
+
*/
|
|
33
|
+
url: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Event data when a deep link is received
|
|
37
|
+
*/
|
|
38
|
+
interface DeepLinkEvent {
|
|
39
|
+
/**
|
|
40
|
+
* The full URL that was used to open the app, or null if no URL was present
|
|
41
|
+
*/
|
|
42
|
+
url: string | null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Listener function for deep link events
|
|
46
|
+
*/
|
|
47
|
+
type DeepLinkListener = (event: DeepLinkEvent) => void;
|
|
48
|
+
/**
|
|
49
|
+
* The Quiltt Connector Capacitor plugin interface.
|
|
50
|
+
*
|
|
51
|
+
* This plugin handles native functionality required for the Quiltt Connector:
|
|
52
|
+
* - Opening OAuth URLs in the system browser
|
|
53
|
+
* - Handling deep links / App Links / Universal Links for OAuth callbacks
|
|
54
|
+
*/
|
|
55
|
+
interface QuilttConnectorPlugin {
|
|
56
|
+
/**
|
|
57
|
+
* Open a URL in the system browser.
|
|
58
|
+
*
|
|
59
|
+
* This is used for OAuth flows where the user needs to authenticate
|
|
60
|
+
* with their financial institution in an external browser.
|
|
61
|
+
*
|
|
62
|
+
* @param options - The options containing the URL to open
|
|
63
|
+
* @returns A promise that resolves when the browser is opened
|
|
64
|
+
*
|
|
65
|
+
* @since 5.0.3
|
|
66
|
+
*/
|
|
67
|
+
openUrl(options: OpenUrlOptions): Promise<{
|
|
68
|
+
completed: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Get the URL that was used to launch the app, if any.
|
|
72
|
+
*
|
|
73
|
+
* This is useful for handling OAuth callbacks when the app is opened
|
|
74
|
+
* from a Universal Link (iOS) or App Link (Android).
|
|
75
|
+
*
|
|
76
|
+
* @returns A promise that resolves with the launch URL, or undefined if none
|
|
77
|
+
*
|
|
78
|
+
* @since 5.0.3
|
|
79
|
+
*/
|
|
80
|
+
getLaunchUrl(): Promise<DeepLinkEvent>;
|
|
81
|
+
/**
|
|
82
|
+
* Listen for deep link events.
|
|
83
|
+
*
|
|
84
|
+
* This is called when the app is opened via a Universal Link (iOS)
|
|
85
|
+
* or App Link (Android), typically during OAuth callback flows.
|
|
86
|
+
*
|
|
87
|
+
* @param eventName - The event name ('deepLink')
|
|
88
|
+
* @param listenerFunc - The callback function to handle the event
|
|
89
|
+
* @returns A promise that resolves with a handle to remove the listener
|
|
90
|
+
*
|
|
91
|
+
* @since 5.0.3
|
|
92
|
+
*/
|
|
93
|
+
addListener(eventName: 'deepLink', listenerFunc: DeepLinkListener): Promise<PluginListenerHandle>;
|
|
94
|
+
/**
|
|
95
|
+
* Remove all listeners for this plugin.
|
|
96
|
+
*
|
|
97
|
+
* @since 5.0.3
|
|
98
|
+
*/
|
|
99
|
+
removeAllListeners(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Native Capacitor plugin for deep link handling and URL opening
|
|
104
|
+
* Used internally by QuilttConnector component for OAuth flows
|
|
105
|
+
*/
|
|
106
|
+
declare const QuilttConnector: QuilttConnectorPlugin;
|
|
107
|
+
|
|
108
|
+
export { QuilttConnector$1 as QuilttConnector, QuilttConnector as QuilttConnectorPlugin };
|
|
109
|
+
export type { DeepLinkEvent, DeepLinkListener, OpenUrlOptions, QuilttConnectorHandle };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { useQuilttSession, ConnectorSDKEventType } from '@quiltt/react';
|
|
2
|
+
export * from '@quiltt/react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { forwardRef, useRef, useState, useMemo, useEffect, useCallback, useImperativeHandle } from 'react';
|
|
5
|
+
import { registerPlugin } from '@capacitor/core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Native Capacitor plugin for deep link handling and URL opening
|
|
9
|
+
* Used internally by QuilttConnector component for OAuth flows
|
|
10
|
+
*/ const QuilttConnector$1 = registerPlugin('QuilttConnector', {
|
|
11
|
+
web: ()=>import('./web-CUWsqcUV.js').then((m)=>new m.QuilttConnectorWeb())
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const trustedQuilttHostSuffixes = [
|
|
15
|
+
'quiltt.io',
|
|
16
|
+
'quiltt.dev',
|
|
17
|
+
'quiltt.app'
|
|
18
|
+
];
|
|
19
|
+
const isTrustedQuilttOrigin = (origin)=>{
|
|
20
|
+
try {
|
|
21
|
+
const originUrl = new URL(origin);
|
|
22
|
+
if (originUrl.protocol !== 'https:') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const hostname = originUrl.hostname.toLowerCase();
|
|
26
|
+
return trustedQuilttHostSuffixes.some((suffix)=>hostname === suffix || hostname.endsWith(`.${suffix}`));
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const decodeIfEncoded = (value)=>{
|
|
32
|
+
try {
|
|
33
|
+
const decoded = decodeURIComponent(value);
|
|
34
|
+
return decoded === value ? value : decoded;
|
|
35
|
+
} catch {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const normalizeUrlValue = (value)=>decodeIfEncoded(value.trim());
|
|
40
|
+
/**
|
|
41
|
+
* QuilttConnector component for Capacitor apps
|
|
42
|
+
* Embeds the Quiltt Connector in an iframe and handles OAuth flows via native plugins
|
|
43
|
+
*/ const QuilttConnector = /*#__PURE__*/ forwardRef(({ connectorId, connectionId, institution, appLauncherUrl, style, className, onEvent, onLoad, onExit, onExitSuccess, onExitAbort, onExitError }, ref)=>{
|
|
44
|
+
const iframeRef = useRef(null);
|
|
45
|
+
const { session } = useQuilttSession();
|
|
46
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
47
|
+
const [loadError, setLoadError] = useState(null);
|
|
48
|
+
// Connector origin for secure postMessage targeting
|
|
49
|
+
const connectorOrigin = useMemo(()=>`https://${connectorId}.quiltt.app`, [
|
|
50
|
+
connectorId
|
|
51
|
+
]);
|
|
52
|
+
// Build connector URL
|
|
53
|
+
const connectorUrl = useMemo(()=>{
|
|
54
|
+
const url = new URL(connectorOrigin);
|
|
55
|
+
if (session?.token) {
|
|
56
|
+
url.searchParams.set('token', session.token);
|
|
57
|
+
}
|
|
58
|
+
if (connectionId) {
|
|
59
|
+
url.searchParams.set('connectionId', connectionId);
|
|
60
|
+
}
|
|
61
|
+
if (institution) {
|
|
62
|
+
url.searchParams.set('institution', institution);
|
|
63
|
+
}
|
|
64
|
+
if (appLauncherUrl) {
|
|
65
|
+
url.searchParams.set('app_launcher_url', normalizeUrlValue(appLauncherUrl));
|
|
66
|
+
}
|
|
67
|
+
if (typeof window !== 'undefined') {
|
|
68
|
+
url.searchParams.set('embed_location', window.location.href);
|
|
69
|
+
}
|
|
70
|
+
// Set mode for inline iframe embedding
|
|
71
|
+
url.searchParams.set('mode', 'INLINE');
|
|
72
|
+
return url.toString();
|
|
73
|
+
}, [
|
|
74
|
+
connectorOrigin,
|
|
75
|
+
session?.token,
|
|
76
|
+
connectionId,
|
|
77
|
+
institution,
|
|
78
|
+
appLauncherUrl
|
|
79
|
+
]);
|
|
80
|
+
useEffect(()=>{
|
|
81
|
+
setIsLoaded(false);
|
|
82
|
+
setLoadError(null);
|
|
83
|
+
const abortController = new AbortController();
|
|
84
|
+
const runPreflight = async ()=>{
|
|
85
|
+
try {
|
|
86
|
+
await fetch(connectorUrl, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
mode: 'no-cors',
|
|
89
|
+
credentials: 'omit',
|
|
90
|
+
signal: abortController.signal
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
setLoadError('Unable to reach Quiltt Connector. Check network and connector settings.');
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
void runPreflight();
|
|
97
|
+
return ()=>{
|
|
98
|
+
abortController.abort();
|
|
99
|
+
};
|
|
100
|
+
}, [
|
|
101
|
+
connectorUrl
|
|
102
|
+
]);
|
|
103
|
+
useEffect(()=>{
|
|
104
|
+
if (isLoaded || loadError) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const timeoutId = window.setTimeout(()=>{
|
|
108
|
+
setLoadError('Connector took too long to load. Please retry.');
|
|
109
|
+
}, 15000);
|
|
110
|
+
return ()=>{
|
|
111
|
+
window.clearTimeout(timeoutId);
|
|
112
|
+
};
|
|
113
|
+
}, [
|
|
114
|
+
isLoaded,
|
|
115
|
+
loadError
|
|
116
|
+
]);
|
|
117
|
+
const postOAuthCallbackToIframe = useCallback((callbackUrl)=>{
|
|
118
|
+
if (!iframeRef.current?.contentWindow) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const normalizedCallbackUrl = normalizeUrlValue(callbackUrl);
|
|
122
|
+
try {
|
|
123
|
+
const callback = new URL(normalizedCallbackUrl);
|
|
124
|
+
const params = {};
|
|
125
|
+
callback.searchParams.forEach((value, key)=>{
|
|
126
|
+
params[key] = value;
|
|
127
|
+
});
|
|
128
|
+
iframeRef.current.contentWindow.postMessage({
|
|
129
|
+
source: 'quiltt',
|
|
130
|
+
type: 'OAuthCallback',
|
|
131
|
+
data: {
|
|
132
|
+
url: normalizedCallbackUrl,
|
|
133
|
+
params
|
|
134
|
+
}
|
|
135
|
+
}, connectorOrigin);
|
|
136
|
+
} catch {
|
|
137
|
+
iframeRef.current.contentWindow.postMessage({
|
|
138
|
+
source: 'quiltt',
|
|
139
|
+
type: 'OAuthCallback',
|
|
140
|
+
data: {
|
|
141
|
+
url: normalizedCallbackUrl,
|
|
142
|
+
params: {}
|
|
143
|
+
}
|
|
144
|
+
}, connectorOrigin);
|
|
145
|
+
}
|
|
146
|
+
}, [
|
|
147
|
+
connectorOrigin
|
|
148
|
+
]);
|
|
149
|
+
// Handle messages from the iframe
|
|
150
|
+
// The platform MessageBus sends: { source: 'quiltt', type: 'Load'|'ExitSuccess'|..., ...metadata }
|
|
151
|
+
const handleMessage = useCallback((event)=>{
|
|
152
|
+
// Validate origin
|
|
153
|
+
if (!isTrustedQuilttOrigin(event.origin)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const data = event.data || {};
|
|
157
|
+
// Validate message is from Quiltt MessageBus
|
|
158
|
+
if (data.source !== 'quiltt' || !data.type) return;
|
|
159
|
+
const { type, connectionId: msgConnectionId, profileId, connectorSession, url } = data;
|
|
160
|
+
// Build metadata from message fields
|
|
161
|
+
const metadata = {
|
|
162
|
+
connectorId,
|
|
163
|
+
...profileId && {
|
|
164
|
+
profileId
|
|
165
|
+
},
|
|
166
|
+
...msgConnectionId && {
|
|
167
|
+
connectionId: msgConnectionId
|
|
168
|
+
},
|
|
169
|
+
...connectorSession && {
|
|
170
|
+
connectorSession
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
switch(type){
|
|
174
|
+
case 'Load':
|
|
175
|
+
setIsLoaded(true);
|
|
176
|
+
setLoadError(null);
|
|
177
|
+
onEvent?.(ConnectorSDKEventType.Load, metadata);
|
|
178
|
+
onLoad?.(metadata);
|
|
179
|
+
break;
|
|
180
|
+
case 'ExitSuccess':
|
|
181
|
+
onEvent?.(ConnectorSDKEventType.ExitSuccess, metadata);
|
|
182
|
+
onExit?.(ConnectorSDKEventType.ExitSuccess, metadata);
|
|
183
|
+
onExitSuccess?.(metadata);
|
|
184
|
+
break;
|
|
185
|
+
case 'ExitAbort':
|
|
186
|
+
onEvent?.(ConnectorSDKEventType.ExitAbort, metadata);
|
|
187
|
+
onExit?.(ConnectorSDKEventType.ExitAbort, metadata);
|
|
188
|
+
onExitAbort?.(metadata);
|
|
189
|
+
break;
|
|
190
|
+
case 'ExitError':
|
|
191
|
+
onEvent?.(ConnectorSDKEventType.ExitError, metadata);
|
|
192
|
+
onExit?.(ConnectorSDKEventType.ExitError, metadata);
|
|
193
|
+
onExitError?.(metadata);
|
|
194
|
+
break;
|
|
195
|
+
case 'Navigate':
|
|
196
|
+
// OAuth URL - open in system browser
|
|
197
|
+
if (url) {
|
|
198
|
+
QuilttConnector$1.openUrl({
|
|
199
|
+
url
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}, [
|
|
205
|
+
connectorId,
|
|
206
|
+
onEvent,
|
|
207
|
+
onLoad,
|
|
208
|
+
onExit,
|
|
209
|
+
onExitSuccess,
|
|
210
|
+
onExitAbort,
|
|
211
|
+
onExitError
|
|
212
|
+
]);
|
|
213
|
+
// Set up message listener
|
|
214
|
+
useEffect(()=>{
|
|
215
|
+
window.addEventListener('message', handleMessage);
|
|
216
|
+
return ()=>window.removeEventListener('message', handleMessage);
|
|
217
|
+
}, [
|
|
218
|
+
handleMessage
|
|
219
|
+
]);
|
|
220
|
+
// Listen for OAuth callbacks via deep links
|
|
221
|
+
useEffect(()=>{
|
|
222
|
+
const listener = QuilttConnector$1.addListener('deepLink', (event)=>{
|
|
223
|
+
if (event.url) {
|
|
224
|
+
postOAuthCallbackToIframe(event.url);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
// Check if app was launched with a URL
|
|
228
|
+
QuilttConnector$1.getLaunchUrl().then((result)=>{
|
|
229
|
+
if (result?.url) {
|
|
230
|
+
postOAuthCallbackToIframe(result.url);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return ()=>{
|
|
234
|
+
listener.then((l)=>l.remove());
|
|
235
|
+
};
|
|
236
|
+
}, [
|
|
237
|
+
postOAuthCallbackToIframe
|
|
238
|
+
]);
|
|
239
|
+
// Expose method to handle OAuth callbacks from parent component
|
|
240
|
+
useImperativeHandle(ref, ()=>({
|
|
241
|
+
handleOAuthCallback: (callbackUrl)=>{
|
|
242
|
+
postOAuthCallbackToIframe(callbackUrl);
|
|
243
|
+
}
|
|
244
|
+
}), [
|
|
245
|
+
postOAuthCallbackToIframe
|
|
246
|
+
]);
|
|
247
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
248
|
+
className: className,
|
|
249
|
+
style: {
|
|
250
|
+
width: '100%',
|
|
251
|
+
height: '100%',
|
|
252
|
+
position: 'relative',
|
|
253
|
+
...style
|
|
254
|
+
},
|
|
255
|
+
children: [
|
|
256
|
+
/*#__PURE__*/ jsx("iframe", {
|
|
257
|
+
ref: iframeRef,
|
|
258
|
+
src: connectorUrl,
|
|
259
|
+
title: "Quiltt Connector",
|
|
260
|
+
allow: "publickey-credentials-get *",
|
|
261
|
+
style: {
|
|
262
|
+
border: 'none',
|
|
263
|
+
width: '100%',
|
|
264
|
+
height: '100%'
|
|
265
|
+
},
|
|
266
|
+
onError: ()=>{
|
|
267
|
+
setLoadError('Unable to load Quiltt Connector iframe.');
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
loadError ? /*#__PURE__*/ jsx("div", {
|
|
271
|
+
style: {
|
|
272
|
+
position: 'absolute',
|
|
273
|
+
inset: 0,
|
|
274
|
+
display: 'flex',
|
|
275
|
+
alignItems: 'center',
|
|
276
|
+
justifyContent: 'center',
|
|
277
|
+
padding: '16px',
|
|
278
|
+
textAlign: 'center',
|
|
279
|
+
backgroundColor: '#fff'
|
|
280
|
+
},
|
|
281
|
+
children: loadError
|
|
282
|
+
}) : null
|
|
283
|
+
]
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
QuilttConnector.displayName = 'QuilttConnector';
|
|
287
|
+
|
|
288
|
+
export { QuilttConnector, QuilttConnector$1 as QuilttConnectorPlugin };
|