@quiltt/vue 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/README.md +212 -0
- package/dist/components/index.cjs +707 -0
- package/dist/components/index.d.ts +278 -0
- package/dist/components/index.js +703 -0
- package/dist/composables/index.cjs +617 -0
- package/dist/composables/index.d.ts +191 -0
- package/dist/composables/index.js +609 -0
- package/dist/index.cjs +75 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/plugin/index.cjs +176 -0
- package/dist/plugin/index.d.ts +48 -0
- package/dist/plugin/index.js +171 -0
- package/package.json +81 -0
- package/src/components/QuilttButton.ts +121 -0
- package/src/components/QuilttConnector.ts +215 -0
- package/src/components/QuilttContainer.ts +130 -0
- package/src/components/index.ts +3 -0
- package/src/composables/index.ts +7 -0
- package/src/composables/useQuilttConnector.ts +312 -0
- package/src/composables/useQuilttInstitutions.ts +114 -0
- package/src/composables/useQuilttResolvable.ts +94 -0
- package/src/composables/useQuilttSession.ts +239 -0
- package/src/composables/useQuilttSettings.ts +15 -0
- package/src/composables/useSession.ts +74 -0
- package/src/composables/useStorage.ts +47 -0
- package/src/constants/deprecation-warnings.ts +2 -0
- package/src/index.ts +34 -0
- package/src/plugin/QuilttPlugin.ts +204 -0
- package/src/plugin/index.ts +23 -0
- package/src/plugin/keys.ts +26 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/telemetry.ts +73 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuilttConnector - Embeds the Quiltt Connector in an iframe
|
|
3
|
+
*
|
|
4
|
+
* This component renders the Quiltt Connector directly in your page,
|
|
5
|
+
* suitable for full-page or embedded connector experiences.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```vue
|
|
9
|
+
* <QuilttConnector
|
|
10
|
+
* :connector-id="connectorId"
|
|
11
|
+
* @exit-success="handleSuccess"
|
|
12
|
+
* />
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { PropType } from 'vue'
|
|
17
|
+
import { computed, defineComponent, h, onMounted, onUnmounted, ref } from 'vue'
|
|
18
|
+
|
|
19
|
+
import type { ConnectorSDKCallbackMetadata, ConnectorSDKEventType } from '@quiltt/core'
|
|
20
|
+
|
|
21
|
+
import { useQuilttSession } from '../composables/useQuilttSession'
|
|
22
|
+
|
|
23
|
+
export interface QuilttConnectorHandle {
|
|
24
|
+
handleOAuthCallback: (url: string) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const QuilttConnector = defineComponent({
|
|
28
|
+
name: 'QuilttConnector',
|
|
29
|
+
|
|
30
|
+
props: {
|
|
31
|
+
/** Quiltt Connector ID */
|
|
32
|
+
connectorId: {
|
|
33
|
+
type: String,
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
/** Existing connection ID for reconnection */
|
|
37
|
+
connectionId: {
|
|
38
|
+
type: String as PropType<string | undefined>,
|
|
39
|
+
default: undefined,
|
|
40
|
+
},
|
|
41
|
+
/** Pre-select a specific institution */
|
|
42
|
+
institution: {
|
|
43
|
+
type: String as PropType<string | undefined>,
|
|
44
|
+
default: undefined,
|
|
45
|
+
},
|
|
46
|
+
/** Deep link URL for OAuth callbacks (mobile apps) */
|
|
47
|
+
appLauncherUrl: {
|
|
48
|
+
type: String as PropType<string | undefined>,
|
|
49
|
+
default: undefined,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
emits: {
|
|
54
|
+
/** Connector loaded */
|
|
55
|
+
load: (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
56
|
+
/** Connection successful */
|
|
57
|
+
'exit-success': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
58
|
+
/** User cancelled */
|
|
59
|
+
'exit-abort': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
60
|
+
/** Error occurred */
|
|
61
|
+
'exit-error': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
62
|
+
/** Any connector event */
|
|
63
|
+
event: (_type: ConnectorSDKEventType, _metadata: ConnectorSDKCallbackMetadata) => true,
|
|
64
|
+
/** OAuth URL requested (for native handling) */
|
|
65
|
+
navigate: (_url: string) => true,
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
setup(props, { emit, expose }) {
|
|
69
|
+
const iframeRef = ref<HTMLIFrameElement>()
|
|
70
|
+
const { session } = useQuilttSession()
|
|
71
|
+
|
|
72
|
+
const trustedQuilttHostSuffixes = ['quiltt.io', 'quiltt.dev', 'quiltt.app']
|
|
73
|
+
|
|
74
|
+
const isTrustedQuilttOrigin = (origin: string): boolean => {
|
|
75
|
+
try {
|
|
76
|
+
const originUrl = new URL(origin)
|
|
77
|
+
if (originUrl.protocol !== 'https:') {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
const hostname = originUrl.hostname.toLowerCase()
|
|
81
|
+
return trustedQuilttHostSuffixes.some(
|
|
82
|
+
(suffix) => hostname === suffix || hostname.endsWith(`.${suffix}`)
|
|
83
|
+
)
|
|
84
|
+
} catch {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Connector origin for secure postMessage targeting
|
|
90
|
+
const connectorOrigin = computed(() => `https://${props.connectorId}.quiltt.app`)
|
|
91
|
+
|
|
92
|
+
// Build connector URL
|
|
93
|
+
const connectorUrl = computed(() => {
|
|
94
|
+
const url = new URL(connectorOrigin.value)
|
|
95
|
+
|
|
96
|
+
if (session.value?.token) {
|
|
97
|
+
url.searchParams.set('token', session.value.token)
|
|
98
|
+
}
|
|
99
|
+
if (props.connectionId) {
|
|
100
|
+
url.searchParams.set('connectionId', props.connectionId)
|
|
101
|
+
}
|
|
102
|
+
if (props.institution) {
|
|
103
|
+
url.searchParams.set('institution', props.institution)
|
|
104
|
+
}
|
|
105
|
+
if (props.appLauncherUrl) {
|
|
106
|
+
url.searchParams.set('app_launcher_url', props.appLauncherUrl)
|
|
107
|
+
}
|
|
108
|
+
if (typeof window !== 'undefined') {
|
|
109
|
+
url.searchParams.set('embed_location', window.location.href)
|
|
110
|
+
}
|
|
111
|
+
// Set mode for inline iframe embedding
|
|
112
|
+
url.searchParams.set('mode', 'INLINE')
|
|
113
|
+
|
|
114
|
+
return url.toString()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Handle messages from the iframe
|
|
118
|
+
// The platform MessageBus sends: { source: 'quiltt', type: 'Load'|'ExitSuccess'|..., ...metadata }
|
|
119
|
+
const handleMessage = (event: MessageEvent) => {
|
|
120
|
+
if (!isTrustedQuilttOrigin(event.origin)) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const data = event.data || {}
|
|
125
|
+
// Validate message is from Quiltt MessageBus
|
|
126
|
+
if (data.source !== 'quiltt' || !data.type) return
|
|
127
|
+
|
|
128
|
+
const { type, connectionId, profileId, connectorSession, url } = data
|
|
129
|
+
|
|
130
|
+
// Build metadata from message fields
|
|
131
|
+
const metadata: ConnectorSDKCallbackMetadata = {
|
|
132
|
+
connectorId: props.connectorId,
|
|
133
|
+
...(profileId && { profileId }),
|
|
134
|
+
...(connectionId && { connectionId }),
|
|
135
|
+
...(connectorSession && { connectorSession }),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
switch (type) {
|
|
139
|
+
case 'Load':
|
|
140
|
+
emit('event', 'Load' as ConnectorSDKEventType, metadata)
|
|
141
|
+
emit('load', metadata)
|
|
142
|
+
break
|
|
143
|
+
case 'ExitSuccess':
|
|
144
|
+
emit('event', 'ExitSuccess' as ConnectorSDKEventType, metadata)
|
|
145
|
+
emit('exit-success', metadata)
|
|
146
|
+
break
|
|
147
|
+
case 'ExitAbort':
|
|
148
|
+
emit('event', 'ExitAbort' as ConnectorSDKEventType, metadata)
|
|
149
|
+
emit('exit-abort', metadata)
|
|
150
|
+
break
|
|
151
|
+
case 'ExitError':
|
|
152
|
+
emit('event', 'ExitError' as ConnectorSDKEventType, metadata)
|
|
153
|
+
emit('exit-error', metadata)
|
|
154
|
+
break
|
|
155
|
+
case 'Navigate':
|
|
156
|
+
if (url) {
|
|
157
|
+
emit('navigate', url)
|
|
158
|
+
}
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Build OAuth callback message matching React Native SDK format
|
|
164
|
+
const buildOAuthCallbackMessage = (callbackUrl: string) => {
|
|
165
|
+
try {
|
|
166
|
+
const parsedUrl = new URL(callbackUrl)
|
|
167
|
+
const params: Record<string, string> = {}
|
|
168
|
+
parsedUrl.searchParams.forEach((value, key) => {
|
|
169
|
+
params[key] = value
|
|
170
|
+
})
|
|
171
|
+
return {
|
|
172
|
+
source: 'quiltt',
|
|
173
|
+
type: 'OAuthCallback',
|
|
174
|
+
data: { url: callbackUrl, params },
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
return {
|
|
178
|
+
source: 'quiltt',
|
|
179
|
+
type: 'OAuthCallback',
|
|
180
|
+
data: { url: callbackUrl, params: {} },
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const handleOAuthCallback = (url: string) => {
|
|
186
|
+
iframeRef.value?.contentWindow?.postMessage(
|
|
187
|
+
buildOAuthCallbackMessage(url),
|
|
188
|
+
connectorOrigin.value
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
expose({ handleOAuthCallback })
|
|
193
|
+
|
|
194
|
+
onMounted(() => {
|
|
195
|
+
window.addEventListener('message', handleMessage)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
onUnmounted(() => {
|
|
199
|
+
window.removeEventListener('message', handleMessage)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
return () =>
|
|
203
|
+
h('iframe', {
|
|
204
|
+
ref: iframeRef,
|
|
205
|
+
src: connectorUrl.value,
|
|
206
|
+
allow: 'publickey-credentials-get *',
|
|
207
|
+
class: 'quiltt-connector',
|
|
208
|
+
style: {
|
|
209
|
+
border: 'none',
|
|
210
|
+
width: '100%',
|
|
211
|
+
height: '100%',
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuilttContainer - Container component that renders Quiltt Connector inline
|
|
3
|
+
*
|
|
4
|
+
* Renders a container element where the Quiltt Connector will be displayed.
|
|
5
|
+
* The connector opens automatically when the component mounts.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```vue
|
|
9
|
+
* <QuilttContainer
|
|
10
|
+
* :connector-id="connectorId"
|
|
11
|
+
* @exit-success="handleSuccess"
|
|
12
|
+
* />
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { computed, defineComponent, h, onMounted, onUnmounted, type PropType, watch } from 'vue'
|
|
17
|
+
|
|
18
|
+
import type { ConnectorSDKCallbackMetadata, ConnectorSDKEventType } from '@quiltt/core'
|
|
19
|
+
|
|
20
|
+
import { useQuilttConnector } from '../composables/useQuilttConnector'
|
|
21
|
+
import { oauthRedirectUrlDeprecationWarning } from '../constants/deprecation-warnings'
|
|
22
|
+
|
|
23
|
+
export const QuilttContainer = defineComponent({
|
|
24
|
+
name: 'QuilttContainer',
|
|
25
|
+
|
|
26
|
+
props: {
|
|
27
|
+
/** Quiltt Connector ID */
|
|
28
|
+
connectorId: {
|
|
29
|
+
type: String,
|
|
30
|
+
required: true,
|
|
31
|
+
},
|
|
32
|
+
/** Existing connection ID for reconnection */
|
|
33
|
+
connectionId: {
|
|
34
|
+
type: String as PropType<string | undefined>,
|
|
35
|
+
default: undefined,
|
|
36
|
+
},
|
|
37
|
+
/** Pre-select a specific institution */
|
|
38
|
+
institution: {
|
|
39
|
+
type: String as PropType<string | undefined>,
|
|
40
|
+
default: undefined,
|
|
41
|
+
},
|
|
42
|
+
/** Deep link URL for OAuth callbacks (mobile apps) */
|
|
43
|
+
appLauncherUrl: {
|
|
44
|
+
type: String as PropType<string | undefined>,
|
|
45
|
+
default: undefined,
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
|
|
49
|
+
* The OAuth redirect URL for mobile or embedded webview flows.
|
|
50
|
+
*/
|
|
51
|
+
oauthRedirectUrl: {
|
|
52
|
+
type: String as PropType<string | undefined>,
|
|
53
|
+
default: undefined,
|
|
54
|
+
},
|
|
55
|
+
/** Render as a different element */
|
|
56
|
+
as: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: 'div',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
emits: {
|
|
63
|
+
/** Connector loaded */
|
|
64
|
+
load: (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
65
|
+
/** Connection successful */
|
|
66
|
+
'exit-success': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
67
|
+
/** User cancelled */
|
|
68
|
+
'exit-abort': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
69
|
+
/** Error occurred */
|
|
70
|
+
'exit-error': (_metadata: ConnectorSDKCallbackMetadata) => true,
|
|
71
|
+
/** Connector exited (any reason) */
|
|
72
|
+
exit: (_type: ConnectorSDKEventType, _metadata: ConnectorSDKCallbackMetadata) => true,
|
|
73
|
+
/** Any connector event */
|
|
74
|
+
event: (_type: ConnectorSDKEventType, _metadata: ConnectorSDKCallbackMetadata) => true,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
setup(props, { emit, slots }) {
|
|
78
|
+
watch(
|
|
79
|
+
() => props.oauthRedirectUrl,
|
|
80
|
+
(value) => {
|
|
81
|
+
if (value !== undefined) {
|
|
82
|
+
console.warn(oauthRedirectUrlDeprecationWarning)
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{ immediate: true }
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const effectiveAppLauncherUri = computed(() => props.appLauncherUrl ?? props.oauthRedirectUrl)
|
|
89
|
+
let openTimeout: ReturnType<typeof setTimeout> | undefined
|
|
90
|
+
|
|
91
|
+
const { open } = useQuilttConnector(() => props.connectorId, {
|
|
92
|
+
connectionId: () => props.connectionId,
|
|
93
|
+
institution: () => props.institution,
|
|
94
|
+
appLauncherUrl: effectiveAppLauncherUri,
|
|
95
|
+
onEvent: (type, metadata) => emit('event', type, metadata),
|
|
96
|
+
onLoad: (metadata) => emit('load', metadata),
|
|
97
|
+
onExit: (type, metadata) => emit('exit', type, metadata),
|
|
98
|
+
onExitSuccess: (metadata) => emit('exit-success', metadata),
|
|
99
|
+
onExitAbort: (metadata) => emit('exit-abort', metadata),
|
|
100
|
+
onExitError: (metadata) => emit('exit-error', metadata),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
onMounted(() => {
|
|
104
|
+
// Short delay to ensure SDK is loaded
|
|
105
|
+
openTimeout = setTimeout(() => {
|
|
106
|
+
open()
|
|
107
|
+
}, 100)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
onUnmounted(() => {
|
|
111
|
+
if (openTimeout) {
|
|
112
|
+
clearTimeout(openTimeout)
|
|
113
|
+
openTimeout = undefined
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return () =>
|
|
118
|
+
h(
|
|
119
|
+
props.as,
|
|
120
|
+
{
|
|
121
|
+
class: 'quiltt-container',
|
|
122
|
+
style: {
|
|
123
|
+
width: '100%',
|
|
124
|
+
height: '100%',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
slots.default?.()
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
})
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quiltt Connector Composable
|
|
3
|
+
*
|
|
4
|
+
* Provides connector management functionality for Vue 3 applications.
|
|
5
|
+
* Loads the Quiltt Connector SDK and manages connector state.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```vue
|
|
9
|
+
* <script setup>
|
|
10
|
+
* import { useQuilttConnector } from '@quiltt/vue'
|
|
11
|
+
*
|
|
12
|
+
* const { open } = useQuilttConnector('conn_xxx', {
|
|
13
|
+
* onExitSuccess: (metadata) => {
|
|
14
|
+
* console.log('Connected:', metadata.connectionId)
|
|
15
|
+
* }
|
|
16
|
+
* })
|
|
17
|
+
* </script>
|
|
18
|
+
*
|
|
19
|
+
* <template>
|
|
20
|
+
* <button @click="open">Connect Bank Account</button>
|
|
21
|
+
* </template>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
26
|
+
import { onMounted, onUnmounted, ref, toValue, watch } from 'vue'
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
ConnectorSDK,
|
|
30
|
+
ConnectorSDKCallbacks,
|
|
31
|
+
ConnectorSDKConnector,
|
|
32
|
+
Maybe,
|
|
33
|
+
QuilttJWT,
|
|
34
|
+
} from '@quiltt/core'
|
|
35
|
+
import { cdnBase } from '@quiltt/core'
|
|
36
|
+
import { extractVersionNumber } from '@quiltt/core/utils'
|
|
37
|
+
|
|
38
|
+
import { oauthRedirectUrlDeprecationWarning } from '../constants/deprecation-warnings'
|
|
39
|
+
import { getSDKAgent } from '../utils'
|
|
40
|
+
import { version } from '../version'
|
|
41
|
+
import { useQuilttSession } from './useQuilttSession'
|
|
42
|
+
|
|
43
|
+
declare const Quiltt: ConnectorSDK
|
|
44
|
+
|
|
45
|
+
export interface UseQuilttConnectorOptions extends ConnectorSDKCallbacks {
|
|
46
|
+
connectionId?: MaybeRefOrGetter<string | undefined>
|
|
47
|
+
institution?: MaybeRefOrGetter<string | undefined>
|
|
48
|
+
appLauncherUrl?: MaybeRefOrGetter<string | undefined>
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
|
|
51
|
+
* The OAuth redirect URL for mobile or embedded webview flows.
|
|
52
|
+
*/
|
|
53
|
+
oauthRedirectUrl?: MaybeRefOrGetter<string | undefined>
|
|
54
|
+
nonce?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface UseQuilttConnectorReturn {
|
|
58
|
+
/** Open the connector modal */
|
|
59
|
+
open: () => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load the Quiltt SDK script
|
|
64
|
+
*/
|
|
65
|
+
const loadScript = (src: string, nonce?: string): Promise<void> => {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
// Check if already loaded
|
|
68
|
+
if (typeof Quiltt !== 'undefined') {
|
|
69
|
+
resolve()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if script is already in DOM
|
|
74
|
+
const existing = document.querySelector(`script[src^="${src.split('?')[0]}"]`)
|
|
75
|
+
if (existing) {
|
|
76
|
+
existing.addEventListener('load', () => resolve())
|
|
77
|
+
existing.addEventListener('error', () => reject(new Error('Failed to load Quiltt SDK')))
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const script = document.createElement('script')
|
|
82
|
+
script.src = src
|
|
83
|
+
script.async = true
|
|
84
|
+
if (nonce) script.nonce = nonce
|
|
85
|
+
script.onload = () => resolve()
|
|
86
|
+
script.onerror = () => reject(new Error('Failed to load Quiltt SDK'))
|
|
87
|
+
document.head.appendChild(script)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Composable for managing Quiltt Connector
|
|
93
|
+
*
|
|
94
|
+
* Loads the Quiltt SDK script and provides methods to open/manage connectors.
|
|
95
|
+
* This composable can run without QuilttPlugin session context; when unavailable,
|
|
96
|
+
* it logs a warning and continues without authenticated session state.
|
|
97
|
+
*/
|
|
98
|
+
export const useQuilttConnector = (
|
|
99
|
+
connectorId?: MaybeRefOrGetter<string | undefined>,
|
|
100
|
+
options?: UseQuilttConnectorOptions
|
|
101
|
+
): UseQuilttConnectorReturn => {
|
|
102
|
+
const getConnectorId = (): string | undefined => toValue(connectorId)
|
|
103
|
+
const getConnectionId = (): string | undefined => toValue(options?.connectionId)
|
|
104
|
+
const getInstitution = (): string | undefined => toValue(options?.institution)
|
|
105
|
+
const getOauthRedirectUrl = (): string | undefined => toValue(options?.oauthRedirectUrl)
|
|
106
|
+
const getAppLauncherUri = (): string | undefined =>
|
|
107
|
+
toValue(options?.appLauncherUrl) ?? getOauthRedirectUrl()
|
|
108
|
+
|
|
109
|
+
const session = ref<Maybe<QuilttJWT | undefined>>()
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const quilttSession = useQuilttSession()
|
|
113
|
+
session.value = quilttSession.session.value
|
|
114
|
+
|
|
115
|
+
watch(
|
|
116
|
+
() => quilttSession.session.value,
|
|
117
|
+
(nextSession) => {
|
|
118
|
+
session.value = nextSession
|
|
119
|
+
},
|
|
120
|
+
{ immediate: true }
|
|
121
|
+
)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn(
|
|
124
|
+
'[Quiltt] useQuilttConnector: QuilttPlugin not found in the current app context. ' +
|
|
125
|
+
'Continuing without session authentication.',
|
|
126
|
+
error
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const connector = ref<ConnectorSDKConnector | undefined>()
|
|
131
|
+
const isLoaded = ref(false)
|
|
132
|
+
const isOpening = ref(false)
|
|
133
|
+
const isConnectorOpen = ref(false)
|
|
134
|
+
|
|
135
|
+
// Track previous values
|
|
136
|
+
let prevConnectionId = getConnectionId()
|
|
137
|
+
let prevConnectorId = getConnectorId()
|
|
138
|
+
let prevInstitution = getInstitution()
|
|
139
|
+
let prevAppLauncherUri = getAppLauncherUri()
|
|
140
|
+
let connectorCreated = false
|
|
141
|
+
|
|
142
|
+
// Load SDK script on mount
|
|
143
|
+
onMounted(async () => {
|
|
144
|
+
const sdkVersion = extractVersionNumber(version)
|
|
145
|
+
const userAgent = getSDKAgent(sdkVersion)
|
|
146
|
+
const scriptUrl = `${cdnBase}/v1/connector.js?agent=${encodeURIComponent(userAgent)}`
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await loadScript(scriptUrl, options?.nonce)
|
|
150
|
+
isLoaded.value = true
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('[Quiltt] Failed to load SDK:', error)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Update authentication when session changes
|
|
157
|
+
watch(
|
|
158
|
+
() => session.value?.token,
|
|
159
|
+
(token) => {
|
|
160
|
+
if (typeof Quiltt !== 'undefined') {
|
|
161
|
+
Quiltt.authenticate(token)
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ immediate: true }
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// Handle script loaded
|
|
168
|
+
watch(
|
|
169
|
+
isLoaded,
|
|
170
|
+
(loaded) => {
|
|
171
|
+
if (!loaded || typeof Quiltt === 'undefined') return
|
|
172
|
+
|
|
173
|
+
// Authenticate with current session
|
|
174
|
+
Quiltt.authenticate(session.value?.token)
|
|
175
|
+
},
|
|
176
|
+
{ immediate: true }
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
watch(
|
|
180
|
+
getOauthRedirectUrl,
|
|
181
|
+
(oauthRedirectUrl) => {
|
|
182
|
+
if (oauthRedirectUrl !== undefined) {
|
|
183
|
+
console.warn(oauthRedirectUrlDeprecationWarning)
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{ immediate: true }
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
// Create/update connector when needed
|
|
190
|
+
const updateConnector = () => {
|
|
191
|
+
const currentConnectorId = getConnectorId()
|
|
192
|
+
if (!isLoaded.value || typeof Quiltt === 'undefined' || !currentConnectorId) return
|
|
193
|
+
|
|
194
|
+
const currentConnectionId = getConnectionId()
|
|
195
|
+
const currentInstitution = getInstitution()
|
|
196
|
+
const currentAppLauncherUri = getAppLauncherUri()
|
|
197
|
+
|
|
198
|
+
// Check for changes
|
|
199
|
+
const connectionIdChanged = prevConnectionId !== currentConnectionId
|
|
200
|
+
const connectorIdChanged = prevConnectorId !== currentConnectorId
|
|
201
|
+
const institutionChanged = prevInstitution !== currentInstitution
|
|
202
|
+
const appLauncherUrlChanged = prevAppLauncherUri !== currentAppLauncherUri
|
|
203
|
+
const hasChanges =
|
|
204
|
+
connectionIdChanged ||
|
|
205
|
+
connectorIdChanged ||
|
|
206
|
+
institutionChanged ||
|
|
207
|
+
appLauncherUrlChanged ||
|
|
208
|
+
!connectorCreated
|
|
209
|
+
|
|
210
|
+
if (hasChanges) {
|
|
211
|
+
if (currentConnectionId) {
|
|
212
|
+
// Reconnect mode
|
|
213
|
+
connector.value = Quiltt.reconnect(currentConnectorId, {
|
|
214
|
+
connectionId: currentConnectionId,
|
|
215
|
+
appLauncherUrl: currentAppLauncherUri,
|
|
216
|
+
})
|
|
217
|
+
} else {
|
|
218
|
+
// Connect mode
|
|
219
|
+
connector.value = Quiltt.connect(currentConnectorId, {
|
|
220
|
+
institution: currentInstitution,
|
|
221
|
+
appLauncherUrl: currentAppLauncherUri,
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
connectorCreated = true
|
|
226
|
+
prevConnectionId = currentConnectionId
|
|
227
|
+
prevConnectorId = currentConnectorId
|
|
228
|
+
prevInstitution = currentInstitution
|
|
229
|
+
prevAppLauncherUri = currentAppLauncherUri
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Watch for changes that require connector update
|
|
234
|
+
watch(
|
|
235
|
+
[isLoaded, getConnectorId, getConnectionId, getInstitution, getAppLauncherUri],
|
|
236
|
+
updateConnector,
|
|
237
|
+
{
|
|
238
|
+
immediate: true,
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
// Register event handlers when connector changes
|
|
243
|
+
watch(
|
|
244
|
+
connector,
|
|
245
|
+
(newConnector, oldConnector) => {
|
|
246
|
+
// Cleanup old handlers
|
|
247
|
+
if (oldConnector) {
|
|
248
|
+
// Note: Quiltt SDK handles cleanup internally
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!newConnector) return
|
|
252
|
+
|
|
253
|
+
// Register handlers
|
|
254
|
+
if (options?.onEvent) {
|
|
255
|
+
newConnector.onEvent(options.onEvent)
|
|
256
|
+
}
|
|
257
|
+
newConnector.onOpen((metadata) => {
|
|
258
|
+
isConnectorOpen.value = true
|
|
259
|
+
options?.onOpen?.(metadata)
|
|
260
|
+
})
|
|
261
|
+
if (options?.onLoad) {
|
|
262
|
+
newConnector.onLoad(options.onLoad)
|
|
263
|
+
}
|
|
264
|
+
newConnector.onExit((type, metadata) => {
|
|
265
|
+
isConnectorOpen.value = false
|
|
266
|
+
options?.onExit?.(type, metadata)
|
|
267
|
+
})
|
|
268
|
+
if (options?.onExitSuccess) {
|
|
269
|
+
newConnector.onExitSuccess(options.onExitSuccess)
|
|
270
|
+
}
|
|
271
|
+
if (options?.onExitAbort) {
|
|
272
|
+
newConnector.onExitAbort(options.onExitAbort)
|
|
273
|
+
}
|
|
274
|
+
if (options?.onExitError) {
|
|
275
|
+
newConnector.onExitError(options.onExitError)
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{ immediate: true }
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Handle deferred opening
|
|
282
|
+
watch([connector, isOpening], ([conn, opening]) => {
|
|
283
|
+
if (conn && opening) {
|
|
284
|
+
isOpening.value = false
|
|
285
|
+
conn.open()
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Warn on unmount if connector is still open
|
|
290
|
+
onUnmounted(() => {
|
|
291
|
+
if (isConnectorOpen.value) {
|
|
292
|
+
console.error(
|
|
293
|
+
'[Quiltt] useQuilttConnector: Component unmounted while Connector is still open. ' +
|
|
294
|
+
'This may lead to memory leaks or unexpected behavior.'
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Open the connector modal
|
|
301
|
+
*/
|
|
302
|
+
const open = (): void => {
|
|
303
|
+
if (getConnectorId()) {
|
|
304
|
+
isOpening.value = true
|
|
305
|
+
updateConnector()
|
|
306
|
+
} else {
|
|
307
|
+
throw new Error('Must provide connectorId to open Quiltt Connector')
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { open }
|
|
312
|
+
}
|