@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.
@@ -0,0 +1,114 @@
1
+ import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
2
+
3
+ import type { ErrorData, InstitutionsData } from '@quiltt/core'
4
+ import { ConnectorsAPI } from '@quiltt/core'
5
+ import { extractVersionNumber } from '@quiltt/core/utils'
6
+
7
+ import { getSDKAgent } from '../utils'
8
+ import { version } from '../version'
9
+ import { useQuilttSession } from './useQuilttSession'
10
+
11
+ /**
12
+ * Search institutions for a connector.
13
+ * Requires QuilttPlugin session context and throws when used without it.
14
+ */
15
+ export const useQuilttInstitutions = (
16
+ connectorId: string,
17
+ onErrorCallback?: (msg: string) => void
18
+ ) => {
19
+ const { session } = useQuilttSession()
20
+
21
+ const searchTermInput = ref('')
22
+ const searchTerm = ref('')
23
+ const searchResults = ref<InstitutionsData>([])
24
+ const isSearching = ref(false)
25
+
26
+ const debounceTimer = ref<ReturnType<typeof setTimeout> | undefined>()
27
+ const abortController = shallowRef<AbortController | undefined>()
28
+
29
+ const sdkVersion = extractVersionNumber(version)
30
+ const connectorsAPI = new ConnectorsAPI(connectorId, getSDKAgent(sdkVersion))
31
+
32
+ const handleError = (message: string) => {
33
+ const errorMessage = message || 'Unknown error occurred while searching institutions'
34
+ console.error('Quiltt Institutions Search Error:', errorMessage)
35
+ onErrorCallback?.(errorMessage)
36
+ }
37
+
38
+ const setSearchTerm = (term: string) => {
39
+ searchTermInput.value = term
40
+
41
+ if (debounceTimer.value) {
42
+ clearTimeout(debounceTimer.value)
43
+ }
44
+
45
+ if (term.trim().length < 2) {
46
+ abortController.value?.abort()
47
+ abortController.value = undefined
48
+ searchTerm.value = ''
49
+ searchResults.value = []
50
+ isSearching.value = false
51
+ return
52
+ }
53
+
54
+ isSearching.value = true
55
+ debounceTimer.value = setTimeout(() => {
56
+ searchTerm.value = term
57
+ }, 350)
58
+ }
59
+
60
+ watch(
61
+ [() => session.value?.token, searchTerm],
62
+ async ([token, term]) => {
63
+ if (!token || !connectorId || !term || term.trim().length < 2) {
64
+ return
65
+ }
66
+
67
+ abortController.value?.abort()
68
+ const controller = new AbortController()
69
+ abortController.value = controller
70
+
71
+ try {
72
+ const response = await connectorsAPI.searchInstitutions(
73
+ token,
74
+ connectorId,
75
+ term,
76
+ controller.signal
77
+ )
78
+
79
+ if (abortController.value !== controller || controller.signal.aborted) {
80
+ return
81
+ }
82
+
83
+ if (response.status === 200) {
84
+ searchResults.value = response.data as InstitutionsData
85
+ } else {
86
+ handleError((response.data as ErrorData).message || 'Failed to fetch institutions')
87
+ }
88
+ } catch (error: any) {
89
+ if (abortController.value === controller && !controller.signal.aborted) {
90
+ handleError(error?.message)
91
+ }
92
+ } finally {
93
+ if (abortController.value === controller && !controller.signal.aborted) {
94
+ isSearching.value = false
95
+ }
96
+ }
97
+ },
98
+ { immediate: false }
99
+ )
100
+
101
+ onUnmounted(() => {
102
+ if (debounceTimer.value) {
103
+ clearTimeout(debounceTimer.value)
104
+ }
105
+ abortController.value?.abort()
106
+ })
107
+
108
+ return {
109
+ searchTerm: computed(() => searchTerm.value),
110
+ searchResults: computed(() => searchResults.value),
111
+ isSearching: computed(() => isSearching.value),
112
+ setSearchTerm,
113
+ }
114
+ }
@@ -0,0 +1,94 @@
1
+ import { computed, ref } from 'vue'
2
+
3
+ import type { ErrorData, ResolvableData } from '@quiltt/core'
4
+ import { ConnectorsAPI } from '@quiltt/core'
5
+ import { extractVersionNumber } from '@quiltt/core/utils'
6
+
7
+ import { getSDKAgent } from '../utils'
8
+ import { version } from '../version'
9
+ import { useQuilttSession } from './useQuilttSession'
10
+
11
+ type ProviderId = {
12
+ plaid?: string
13
+ mock?: string
14
+ mx?: string
15
+ finicity?: string
16
+ akoya?: string
17
+ }
18
+
19
+ /**
20
+ * Check whether a provider link is resolvable for a connector.
21
+ * Requires QuilttPlugin session context and throws when used without it.
22
+ */
23
+ export const useQuilttResolvable = (
24
+ connectorId: string,
25
+ onErrorCallback?: (msg: string) => void
26
+ ) => {
27
+ const { session } = useQuilttSession()
28
+ const sdkVersion = extractVersionNumber(version)
29
+ const connectorsAPI = new ConnectorsAPI(connectorId, getSDKAgent(sdkVersion))
30
+
31
+ const isLoading = ref(false)
32
+ const isResolvable = ref<boolean | null>(null)
33
+ const error = ref<string | null>(null)
34
+
35
+ const handleError = (message: string) => {
36
+ const errorMessage = message || 'Unknown error occurred while checking resolvability'
37
+
38
+ error.value = errorMessage
39
+ console.error('Quiltt Connector Resolvable Error:', errorMessage)
40
+ onErrorCallback?.(errorMessage)
41
+ }
42
+
43
+ const checkResolvable = async (providerId: ProviderId): Promise<boolean | null> => {
44
+ if (!session.value?.token) {
45
+ handleError('Missing session token')
46
+ return null
47
+ }
48
+
49
+ if (!connectorId) {
50
+ handleError('Missing connector ID')
51
+ return null
52
+ }
53
+
54
+ const hasProviderId = Object.values(providerId).some((id) => !!id)
55
+ if (!hasProviderId) {
56
+ handleError('No provider ID specified')
57
+ return null
58
+ }
59
+
60
+ isLoading.value = true
61
+ error.value = null
62
+
63
+ try {
64
+ const response = await connectorsAPI.checkResolvable(
65
+ session.value.token,
66
+ connectorId,
67
+ providerId
68
+ )
69
+
70
+ if (response.status === 200) {
71
+ const result = (response.data as ResolvableData).resolvable
72
+ isResolvable.value = result
73
+ return result
74
+ }
75
+
76
+ handleError((response.data as ErrorData).message || 'Failed to check resolvability')
77
+ isResolvable.value = null
78
+ return null
79
+ } catch (caught: any) {
80
+ handleError(caught?.message)
81
+ isResolvable.value = null
82
+ return null
83
+ } finally {
84
+ isLoading.value = false
85
+ }
86
+ }
87
+
88
+ return {
89
+ checkResolvable,
90
+ isLoading: computed(() => isLoading.value),
91
+ isResolvable: computed(() => isResolvable.value),
92
+ error: computed(() => error.value),
93
+ }
94
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Quiltt Session Composable
3
+ *
4
+ * Provides session management functionality for Vue 3 applications.
5
+ * Must be used within a component tree where QuilttPlugin is installed.
6
+ *
7
+ * @example
8
+ * ```vue
9
+ * <script setup>
10
+ * import { useQuilttSession } from '@quiltt/vue'
11
+ *
12
+ * const { session, importSession, revokeSession } = useQuilttSession()
13
+ *
14
+ * // Import a session token
15
+ * await importSession('<SESSION_TOKEN>')
16
+ *
17
+ * // Access session data
18
+ * console.log(session.value?.token)
19
+ *
20
+ * // Revoke the session
21
+ * await revokeSession()
22
+ * </script>
23
+ * ```
24
+ */
25
+
26
+ import { type ComputedRef, computed, inject } from 'vue'
27
+
28
+ import type {
29
+ Maybe,
30
+ PasscodePayload,
31
+ PrivateClaims,
32
+ QuilttJWT,
33
+ SessionResponse,
34
+ UnprocessableData,
35
+ UnprocessableResponse,
36
+ UsernamePayload,
37
+ } from '@quiltt/core'
38
+ import { AuthAPI, JsonWebTokenParse } from '@quiltt/core'
39
+
40
+ import { QuilttClientIdKey, QuilttSessionKey, QuilttSetSessionKey } from '../plugin/keys'
41
+
42
+ // Initialize JWT parser
43
+ const parse = JsonWebTokenParse<PrivateClaims>
44
+
45
+ /**
46
+ * Callbacks for identify session operation
47
+ */
48
+ export interface IdentifySessionCallbacks {
49
+ onSuccess?: () => unknown
50
+ onChallenged?: () => unknown
51
+ onError?: (errors: UnprocessableData) => unknown
52
+ onForbidden?: () => unknown
53
+ }
54
+
55
+ /**
56
+ * Callbacks for authenticate session operation
57
+ */
58
+ export interface AuthenticateSessionCallbacks {
59
+ onSuccess?: () => unknown
60
+ onFailure?: () => unknown
61
+ onError?: (errors: UnprocessableData) => unknown
62
+ }
63
+
64
+ export type ImportSession = (token: string, environmentId?: string) => Promise<boolean>
65
+ export type IdentifySession = (
66
+ payload: UsernamePayload,
67
+ callbacks: IdentifySessionCallbacks
68
+ ) => Promise<unknown>
69
+ export type AuthenticateSession = (
70
+ payload: PasscodePayload,
71
+ callbacks: AuthenticateSessionCallbacks
72
+ ) => Promise<unknown>
73
+ export type RevokeSession = () => Promise<void>
74
+ export type ForgetSession = (token?: string) => void
75
+
76
+ export interface UseQuilttSessionReturn {
77
+ /** Current session (reactive) */
78
+ session: ComputedRef<Maybe<QuilttJWT> | undefined>
79
+ /** Import an existing session token */
80
+ importSession: ImportSession
81
+ /** Start authentication flow with username/email/phone */
82
+ identifySession: IdentifySession
83
+ /** Complete authentication with passcode */
84
+ authenticateSession: AuthenticateSession
85
+ /** Revoke the current session (invalidates token on server) */
86
+ revokeSession: RevokeSession
87
+ /** Forget the current session locally (without server call) */
88
+ forgetSession: ForgetSession
89
+ }
90
+
91
+ /**
92
+ * Composable for managing Quiltt session state
93
+ *
94
+ * Provides methods for importing, creating, and revoking sessions.
95
+ * Session state is automatically synchronized across components.
96
+ * Requires QuilttPlugin provider context and throws when used without it.
97
+ */
98
+ export const useQuilttSession = (): UseQuilttSessionReturn => {
99
+ const sessionRef = inject(QuilttSessionKey)
100
+ const setSession = inject(QuilttSetSessionKey)
101
+ const clientIdRef = inject(QuilttClientIdKey)
102
+
103
+ if (!sessionRef || !setSession) {
104
+ throw new Error(
105
+ '[Quiltt] useQuilttSession must be used within a component where QuilttPlugin is installed. ' +
106
+ 'Make sure to call app.use(QuilttPlugin) before mounting your app.'
107
+ )
108
+ }
109
+
110
+ // Create a computed ref for the session
111
+ const session = computed(() => sessionRef.value)
112
+
113
+ // Create AuthAPI instance (memoized based on clientId)
114
+ const getAuth = () => new AuthAPI(clientIdRef?.value)
115
+
116
+ /**
117
+ * Import an existing session token
118
+ * Validates the token and sets it as the current session
119
+ */
120
+ const importSession: ImportSession = async (token, environmentId) => {
121
+ const auth = getAuth()
122
+
123
+ // Is there a token?
124
+ if (!token) return !!sessionRef.value
125
+
126
+ // Is this token already imported?
127
+ if (sessionRef.value && sessionRef.value.token === token) return true
128
+
129
+ const jwt = parse(token)
130
+
131
+ // Is this token a valid JWT?
132
+ if (!jwt) return false
133
+
134
+ // Is this token within the expected environment?
135
+ if (environmentId && jwt.claims.eid !== environmentId) return false
136
+
137
+ // Is this token active?
138
+ const response = await auth.ping(token)
139
+ switch (response.status) {
140
+ case 200:
141
+ setSession(token)
142
+ return true
143
+
144
+ case 401:
145
+ return false
146
+
147
+ default:
148
+ throw new Error(`AuthAPI.ping: Unexpected response status ${response.status}`)
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Start authentication flow with username/email/phone
154
+ * Returns a session token or challenges for passcode
155
+ */
156
+ const identifySession: IdentifySession = async (payload, callbacks) => {
157
+ const auth = getAuth()
158
+ const response = await auth.identify(payload)
159
+
160
+ switch (response.status) {
161
+ case 201: // Created
162
+ setSession((response as SessionResponse).data.token)
163
+ if (callbacks.onSuccess) return callbacks.onSuccess()
164
+ break
165
+
166
+ case 202: // Accepted (needs passcode)
167
+ if (callbacks.onChallenged) return callbacks.onChallenged()
168
+ break
169
+
170
+ case 403: // Forbidden (signups disabled)
171
+ if (callbacks.onForbidden) return callbacks.onForbidden()
172
+ break
173
+
174
+ case 422: // Unprocessable Content
175
+ if (callbacks.onError) return callbacks.onError((response as UnprocessableResponse).data)
176
+ break
177
+
178
+ default:
179
+ throw new Error(`AuthAPI.identify: Unexpected response status ${response.status}`)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Complete authentication with passcode
185
+ */
186
+ const authenticateSession: AuthenticateSession = async (payload, callbacks) => {
187
+ const auth = getAuth()
188
+ const response = await auth.authenticate(payload)
189
+
190
+ switch (response.status) {
191
+ case 201:
192
+ setSession((response as SessionResponse).data.token)
193
+ if (callbacks.onSuccess) return callbacks.onSuccess()
194
+ break
195
+
196
+ case 401:
197
+ if (callbacks.onFailure) return callbacks.onFailure()
198
+ break
199
+
200
+ case 422:
201
+ if (callbacks.onError) return callbacks.onError((response as UnprocessableResponse).data)
202
+ break
203
+
204
+ default:
205
+ throw new Error(`AuthAPI.authenticate: Unexpected response status ${response.status}`)
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Revoke the current session
211
+ * Invalidates the token on the server and clears local state
212
+ */
213
+ const revokeSession: RevokeSession = async () => {
214
+ if (!sessionRef.value) return
215
+
216
+ const auth = getAuth()
217
+ await auth.revoke(sessionRef.value.token)
218
+ setSession(null)
219
+ }
220
+
221
+ /**
222
+ * Forget the current session locally without server call
223
+ * Optionally pass a specific token to guard against async processes clearing wrong session
224
+ */
225
+ const forgetSession: ForgetSession = (token) => {
226
+ if (!token || (sessionRef.value && token === sessionRef.value.token)) {
227
+ setSession(null)
228
+ }
229
+ }
230
+
231
+ return {
232
+ session,
233
+ importSession,
234
+ identifySession,
235
+ authenticateSession,
236
+ revokeSession,
237
+ forgetSession,
238
+ }
239
+ }
@@ -0,0 +1,15 @@
1
+ import { computed, inject } from 'vue'
2
+
3
+ import { QuilttClientIdKey } from '../plugin/keys'
4
+
5
+ /**
6
+ * Read plugin-provided Quiltt settings.
7
+ * When used without QuilttPlugin context, values are undefined.
8
+ */
9
+ export const useQuilttSettings = () => {
10
+ const clientIdRef = inject(QuilttClientIdKey)
11
+
12
+ return {
13
+ clientId: computed(() => clientIdRef?.value),
14
+ }
15
+ }
@@ -0,0 +1,74 @@
1
+ import { computed, onUnmounted, ref, watch } from 'vue'
2
+
3
+ import type { Maybe, PrivateClaims, QuilttJWT } from '@quiltt/core'
4
+ import { JsonWebTokenParse, Timeoutable } from '@quiltt/core'
5
+
6
+ import { useStorage } from './useStorage'
7
+
8
+ const parse = JsonWebTokenParse<PrivateClaims>
9
+ const sessionTimer = new Timeoutable()
10
+
11
+ export const useSession = (storageKey = 'session') => {
12
+ const { storage: token, setStorage } = useStorage<string>(storageKey)
13
+ const session = ref<Maybe<QuilttJWT> | undefined>(parse(token.value))
14
+ let currentExpire: (() => void) | null = null
15
+
16
+ watch(
17
+ token,
18
+ (newToken) => {
19
+ session.value = parse(newToken)
20
+ },
21
+ { immediate: true }
22
+ )
23
+
24
+ watch(
25
+ () => session.value,
26
+ (nextSession) => {
27
+ if (currentExpire) {
28
+ sessionTimer.clear(currentExpire)
29
+ currentExpire = null
30
+ }
31
+
32
+ if (!nextSession) {
33
+ return
34
+ }
35
+
36
+ const expirationMS = nextSession.claims.exp * 1000
37
+ const expire = () => setStorage(null)
38
+ currentExpire = expire
39
+
40
+ if (Date.now() >= expirationMS) {
41
+ expire()
42
+ return
43
+ }
44
+
45
+ sessionTimer.set(expire, expirationMS - Date.now())
46
+ },
47
+ { immediate: true }
48
+ )
49
+
50
+ const setSession = (
51
+ nextState:
52
+ | Maybe<string>
53
+ | undefined
54
+ | ((prev: Maybe<string> | undefined) => Maybe<string> | undefined)
55
+ ) => {
56
+ const resolved = nextState instanceof Function ? nextState(token.value) : nextState
57
+
58
+ if (token.value !== resolved && (!resolved || parse(resolved))) {
59
+ setStorage(resolved)
60
+ }
61
+ }
62
+
63
+ onUnmounted(() => {
64
+ if (currentExpire) {
65
+ sessionTimer.clear(currentExpire)
66
+ currentExpire = null
67
+ }
68
+ })
69
+
70
+ return {
71
+ session: computed(() => session.value),
72
+ setSession,
73
+ }
74
+ }
@@ -0,0 +1,47 @@
1
+ import { computed, getCurrentScope, onScopeDispose, ref } from 'vue'
2
+
3
+ import type { Maybe } from '@quiltt/core'
4
+ import { GlobalStorage } from '@quiltt/core'
5
+
6
+ export const useStorage = <T>(key: string, initialState?: Maybe<T>) => {
7
+ const readStorage = () => {
8
+ const current = GlobalStorage.get(key)
9
+ if (current !== undefined) {
10
+ return current as Maybe<T>
11
+ }
12
+
13
+ return initialState
14
+ }
15
+
16
+ const state = ref<Maybe<T> | undefined>(readStorage())
17
+
18
+ const setStorage = (
19
+ nextState: Maybe<T> | undefined | ((prev: Maybe<T> | undefined) => Maybe<T> | undefined)
20
+ ) => {
21
+ const resolved = nextState instanceof Function ? nextState(state.value) : nextState
22
+
23
+ if (state.value !== resolved) {
24
+ GlobalStorage.set(key, resolved)
25
+ state.value = resolved
26
+ }
27
+ }
28
+
29
+ const handleStorageChange = (newValue: Maybe<T> | undefined) => {
30
+ state.value = newValue
31
+ }
32
+
33
+ GlobalStorage.subscribe(key, handleStorageChange)
34
+
35
+ // Use onScopeDispose for cleanup in any effect scope (components, effectScope(), etc.)
36
+ // This safely handles cases where composable is called outside a scope
37
+ if (getCurrentScope()) {
38
+ onScopeDispose(() => {
39
+ GlobalStorage.unsubscribe(key, handleStorageChange)
40
+ })
41
+ }
42
+
43
+ return {
44
+ storage: computed(() => state.value),
45
+ setStorage,
46
+ }
47
+ }
@@ -0,0 +1,2 @@
1
+ export const oauthRedirectUrlDeprecationWarning =
2
+ '[Quiltt] `oauthRedirectUrl` is deprecated. Use `appLauncherUrl` instead. `oauthRedirectUrl` will be removed in the next major release.'
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ // ============================================================================
2
+ // @quiltt/vue - Vue 3 Composables and Components for Quiltt
3
+ // ============================================================================
4
+ // This package provides Vue 3-specific composables and components for
5
+ // integrating Quiltt's financial data platform into Vue applications.
6
+ // It re-exports all @quiltt/core functionality plus Vue-specific features.
7
+ //
8
+ // Main exports:
9
+ // - All @quiltt/core modules (API clients, auth, config, storage, types)
10
+ // - Vue plugin (QuilttPlugin) for app-wide session management
11
+ // - Vue composables (useQuilttSession, useQuilttConnector)
12
+ // - Vue components (QuilttConnector, QuilttButton, QuilttContainer)
13
+ // ============================================================================
14
+
15
+ // ============================================================================
16
+ // Quiltt Core - Re-export all modules from @quiltt/core
17
+ // ============================================================================
18
+ // Re-export all core Quiltt functionality so users only need to install
19
+ // @quiltt/vue instead of both @quiltt/core and @quiltt/vue.
20
+ export * from '@quiltt/core/api'
21
+ export * from '@quiltt/core/auth'
22
+ export * from '@quiltt/core/config'
23
+ export * from '@quiltt/core/observables'
24
+ export * from '@quiltt/core/storage'
25
+ export * from '@quiltt/core/timing'
26
+ export * from '@quiltt/core/types'
27
+
28
+ // ============================================================================
29
+ // Vue-specific exports
30
+ // ============================================================================
31
+ // Quiltt Vue plugin, composables, and components for Vue 3 applications.
32
+ export * from './components'
33
+ export * from './composables'
34
+ export * from './plugin'