@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,617 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var vue = require('vue');
4
+ var core = require('@quiltt/core');
5
+ var utils = require('@quiltt/core/utils');
6
+
7
+ const oauthRedirectUrlDeprecationWarning = '[Quiltt] `oauthRedirectUrl` is deprecated. Use `appLauncherUrl` instead. `oauthRedirectUrl` will be removed in the next major release.';
8
+
9
+ /**
10
+ * Gets the Vue version from the runtime
11
+ */ const getVueVersion = ()=>{
12
+ return vue.version || 'unknown';
13
+ };
14
+ /**
15
+ * Detects if running in a Capacitor native environment
16
+ */ const getCapacitorInfo = ()=>{
17
+ if (typeof window === 'undefined') return null;
18
+ try {
19
+ if (window.Capacitor?.isNativePlatform?.()) {
20
+ const platform = window.Capacitor.getPlatform?.() || 'native';
21
+ // Map platform names to correct capitalization
22
+ const platformNames = {
23
+ ios: 'iOS',
24
+ android: 'Android',
25
+ web: 'Web'
26
+ };
27
+ const platformName = platformNames[platform.toLowerCase()] || platform;
28
+ return `Capacitor/${platformName}`;
29
+ }
30
+ } catch {
31
+ // Ignore errors
32
+ }
33
+ return null;
34
+ };
35
+ /**
36
+ * Generates platform information string for Vue web
37
+ * Format: Vue/<version>; <browser>/<version>
38
+ * Or with Capacitor: Vue/<version>; Capacitor/<platform>; <browser>/<version>
39
+ */ const getPlatformInfo = ()=>{
40
+ const versionStr = getVueVersion();
41
+ const capacitorInfo = getCapacitorInfo();
42
+ const browserInfo = utils.getBrowserInfo();
43
+ if (capacitorInfo) {
44
+ return `Vue/${versionStr}; ${capacitorInfo}; ${browserInfo}`;
45
+ }
46
+ return `Vue/${versionStr}; ${browserInfo}`;
47
+ };
48
+ /**
49
+ * Generates User-Agent string for Vue SDK
50
+ * Format: Quiltt/<sdk-version> (Vue/<vue-version>; <browser>/<version>)
51
+ */ const getSDKAgent = (sdkVersion)=>{
52
+ const platformInfo = getPlatformInfo();
53
+ return utils.getSDKAgent(sdkVersion, platformInfo);
54
+ };
55
+
56
+ var version = "5.1.2";
57
+
58
+ /**
59
+ * Injection keys and types for Quiltt Vue plugin
60
+ */ // Injection keys for Quiltt state
61
+ const QuilttSessionKey = Symbol.for('quiltt-session');
62
+ const QuilttSetSessionKey = Symbol.for('quiltt-set-session');
63
+ const QuilttClientIdKey = Symbol.for('quiltt-client-id');
64
+
65
+ // Initialize JWT parser
66
+ const parse$1 = core.JsonWebTokenParse;
67
+ /**
68
+ * Composable for managing Quiltt session state
69
+ *
70
+ * Provides methods for importing, creating, and revoking sessions.
71
+ * Session state is automatically synchronized across components.
72
+ * Requires QuilttPlugin provider context and throws when used without it.
73
+ */ const useQuilttSession = ()=>{
74
+ const sessionRef = vue.inject(QuilttSessionKey);
75
+ const setSession = vue.inject(QuilttSetSessionKey);
76
+ const clientIdRef = vue.inject(QuilttClientIdKey);
77
+ if (!sessionRef || !setSession) {
78
+ throw new Error('[Quiltt] useQuilttSession must be used within a component where QuilttPlugin is installed. ' + 'Make sure to call app.use(QuilttPlugin) before mounting your app.');
79
+ }
80
+ // Create a computed ref for the session
81
+ const session = vue.computed(()=>sessionRef.value);
82
+ // Create AuthAPI instance (memoized based on clientId)
83
+ const getAuth = ()=>new core.AuthAPI(clientIdRef?.value);
84
+ /**
85
+ * Import an existing session token
86
+ * Validates the token and sets it as the current session
87
+ */ const importSession = async (token, environmentId)=>{
88
+ const auth = getAuth();
89
+ // Is there a token?
90
+ if (!token) return !!sessionRef.value;
91
+ // Is this token already imported?
92
+ if (sessionRef.value && sessionRef.value.token === token) return true;
93
+ const jwt = parse$1(token);
94
+ // Is this token a valid JWT?
95
+ if (!jwt) return false;
96
+ // Is this token within the expected environment?
97
+ if (environmentId && jwt.claims.eid !== environmentId) return false;
98
+ // Is this token active?
99
+ const response = await auth.ping(token);
100
+ switch(response.status){
101
+ case 200:
102
+ setSession(token);
103
+ return true;
104
+ case 401:
105
+ return false;
106
+ default:
107
+ throw new Error(`AuthAPI.ping: Unexpected response status ${response.status}`);
108
+ }
109
+ };
110
+ /**
111
+ * Start authentication flow with username/email/phone
112
+ * Returns a session token or challenges for passcode
113
+ */ const identifySession = async (payload, callbacks)=>{
114
+ const auth = getAuth();
115
+ const response = await auth.identify(payload);
116
+ switch(response.status){
117
+ case 201:
118
+ setSession(response.data.token);
119
+ if (callbacks.onSuccess) return callbacks.onSuccess();
120
+ break;
121
+ case 202:
122
+ if (callbacks.onChallenged) return callbacks.onChallenged();
123
+ break;
124
+ case 403:
125
+ if (callbacks.onForbidden) return callbacks.onForbidden();
126
+ break;
127
+ case 422:
128
+ if (callbacks.onError) return callbacks.onError(response.data);
129
+ break;
130
+ default:
131
+ throw new Error(`AuthAPI.identify: Unexpected response status ${response.status}`);
132
+ }
133
+ };
134
+ /**
135
+ * Complete authentication with passcode
136
+ */ const authenticateSession = async (payload, callbacks)=>{
137
+ const auth = getAuth();
138
+ const response = await auth.authenticate(payload);
139
+ switch(response.status){
140
+ case 201:
141
+ setSession(response.data.token);
142
+ if (callbacks.onSuccess) return callbacks.onSuccess();
143
+ break;
144
+ case 401:
145
+ if (callbacks.onFailure) return callbacks.onFailure();
146
+ break;
147
+ case 422:
148
+ if (callbacks.onError) return callbacks.onError(response.data);
149
+ break;
150
+ default:
151
+ throw new Error(`AuthAPI.authenticate: Unexpected response status ${response.status}`);
152
+ }
153
+ };
154
+ /**
155
+ * Revoke the current session
156
+ * Invalidates the token on the server and clears local state
157
+ */ const revokeSession = async ()=>{
158
+ if (!sessionRef.value) return;
159
+ const auth = getAuth();
160
+ await auth.revoke(sessionRef.value.token);
161
+ setSession(null);
162
+ };
163
+ /**
164
+ * Forget the current session locally without server call
165
+ * Optionally pass a specific token to guard against async processes clearing wrong session
166
+ */ const forgetSession = (token)=>{
167
+ if (!token || sessionRef.value && token === sessionRef.value.token) {
168
+ setSession(null);
169
+ }
170
+ };
171
+ return {
172
+ session,
173
+ importSession,
174
+ identifySession,
175
+ authenticateSession,
176
+ revokeSession,
177
+ forgetSession
178
+ };
179
+ };
180
+
181
+ /**
182
+ * Load the Quiltt SDK script
183
+ */ const loadScript = (src, nonce)=>{
184
+ return new Promise((resolve, reject)=>{
185
+ // Check if already loaded
186
+ if (typeof Quiltt !== 'undefined') {
187
+ resolve();
188
+ return;
189
+ }
190
+ // Check if script is already in DOM
191
+ const existing = document.querySelector(`script[src^="${src.split('?')[0]}"]`);
192
+ if (existing) {
193
+ existing.addEventListener('load', ()=>resolve());
194
+ existing.addEventListener('error', ()=>reject(new Error('Failed to load Quiltt SDK')));
195
+ return;
196
+ }
197
+ const script = document.createElement('script');
198
+ script.src = src;
199
+ script.async = true;
200
+ if (nonce) script.nonce = nonce;
201
+ script.onload = ()=>resolve();
202
+ script.onerror = ()=>reject(new Error('Failed to load Quiltt SDK'));
203
+ document.head.appendChild(script);
204
+ });
205
+ };
206
+ /**
207
+ * Composable for managing Quiltt Connector
208
+ *
209
+ * Loads the Quiltt SDK script and provides methods to open/manage connectors.
210
+ * This composable can run without QuilttPlugin session context; when unavailable,
211
+ * it logs a warning and continues without authenticated session state.
212
+ */ const useQuilttConnector = (connectorId, options)=>{
213
+ const getConnectorId = ()=>vue.toValue(connectorId);
214
+ const getConnectionId = ()=>vue.toValue(options?.connectionId);
215
+ const getInstitution = ()=>vue.toValue(options?.institution);
216
+ const getOauthRedirectUrl = ()=>vue.toValue(options?.oauthRedirectUrl);
217
+ const getAppLauncherUri = ()=>vue.toValue(options?.appLauncherUrl) ?? getOauthRedirectUrl();
218
+ const session = vue.ref();
219
+ try {
220
+ const quilttSession = useQuilttSession();
221
+ session.value = quilttSession.session.value;
222
+ vue.watch(()=>quilttSession.session.value, (nextSession)=>{
223
+ session.value = nextSession;
224
+ }, {
225
+ immediate: true
226
+ });
227
+ } catch (error) {
228
+ console.warn('[Quiltt] useQuilttConnector: QuilttPlugin not found in the current app context. ' + 'Continuing without session authentication.', error);
229
+ }
230
+ const connector = vue.ref();
231
+ const isLoaded = vue.ref(false);
232
+ const isOpening = vue.ref(false);
233
+ const isConnectorOpen = vue.ref(false);
234
+ // Track previous values
235
+ let prevConnectionId = getConnectionId();
236
+ let prevConnectorId = getConnectorId();
237
+ let prevInstitution = getInstitution();
238
+ let prevAppLauncherUri = getAppLauncherUri();
239
+ let connectorCreated = false;
240
+ // Load SDK script on mount
241
+ vue.onMounted(async ()=>{
242
+ const sdkVersion = utils.extractVersionNumber(version);
243
+ const userAgent = getSDKAgent(sdkVersion);
244
+ const scriptUrl = `${core.cdnBase}/v1/connector.js?agent=${encodeURIComponent(userAgent)}`;
245
+ try {
246
+ await loadScript(scriptUrl, options?.nonce);
247
+ isLoaded.value = true;
248
+ } catch (error) {
249
+ console.error('[Quiltt] Failed to load SDK:', error);
250
+ }
251
+ });
252
+ // Update authentication when session changes
253
+ vue.watch(()=>session.value?.token, (token)=>{
254
+ if (typeof Quiltt !== 'undefined') {
255
+ Quiltt.authenticate(token);
256
+ }
257
+ }, {
258
+ immediate: true
259
+ });
260
+ // Handle script loaded
261
+ vue.watch(isLoaded, (loaded)=>{
262
+ if (!loaded || typeof Quiltt === 'undefined') return;
263
+ // Authenticate with current session
264
+ Quiltt.authenticate(session.value?.token);
265
+ }, {
266
+ immediate: true
267
+ });
268
+ vue.watch(getOauthRedirectUrl, (oauthRedirectUrl)=>{
269
+ if (oauthRedirectUrl !== undefined) {
270
+ console.warn(oauthRedirectUrlDeprecationWarning);
271
+ }
272
+ }, {
273
+ immediate: true
274
+ });
275
+ // Create/update connector when needed
276
+ const updateConnector = ()=>{
277
+ const currentConnectorId = getConnectorId();
278
+ if (!isLoaded.value || typeof Quiltt === 'undefined' || !currentConnectorId) return;
279
+ const currentConnectionId = getConnectionId();
280
+ const currentInstitution = getInstitution();
281
+ const currentAppLauncherUri = getAppLauncherUri();
282
+ // Check for changes
283
+ const connectionIdChanged = prevConnectionId !== currentConnectionId;
284
+ const connectorIdChanged = prevConnectorId !== currentConnectorId;
285
+ const institutionChanged = prevInstitution !== currentInstitution;
286
+ const appLauncherUrlChanged = prevAppLauncherUri !== currentAppLauncherUri;
287
+ const hasChanges = connectionIdChanged || connectorIdChanged || institutionChanged || appLauncherUrlChanged || !connectorCreated;
288
+ if (hasChanges) {
289
+ if (currentConnectionId) {
290
+ // Reconnect mode
291
+ connector.value = Quiltt.reconnect(currentConnectorId, {
292
+ connectionId: currentConnectionId,
293
+ appLauncherUrl: currentAppLauncherUri
294
+ });
295
+ } else {
296
+ // Connect mode
297
+ connector.value = Quiltt.connect(currentConnectorId, {
298
+ institution: currentInstitution,
299
+ appLauncherUrl: currentAppLauncherUri
300
+ });
301
+ }
302
+ connectorCreated = true;
303
+ prevConnectionId = currentConnectionId;
304
+ prevConnectorId = currentConnectorId;
305
+ prevInstitution = currentInstitution;
306
+ prevAppLauncherUri = currentAppLauncherUri;
307
+ }
308
+ };
309
+ // Watch for changes that require connector update
310
+ vue.watch([
311
+ isLoaded,
312
+ getConnectorId,
313
+ getConnectionId,
314
+ getInstitution,
315
+ getAppLauncherUri
316
+ ], updateConnector, {
317
+ immediate: true
318
+ });
319
+ // Register event handlers when connector changes
320
+ vue.watch(connector, (newConnector, oldConnector)=>{
321
+ if (!newConnector) return;
322
+ // Register handlers
323
+ if (options?.onEvent) {
324
+ newConnector.onEvent(options.onEvent);
325
+ }
326
+ newConnector.onOpen((metadata)=>{
327
+ isConnectorOpen.value = true;
328
+ options?.onOpen?.(metadata);
329
+ });
330
+ if (options?.onLoad) {
331
+ newConnector.onLoad(options.onLoad);
332
+ }
333
+ newConnector.onExit((type, metadata)=>{
334
+ isConnectorOpen.value = false;
335
+ options?.onExit?.(type, metadata);
336
+ });
337
+ if (options?.onExitSuccess) {
338
+ newConnector.onExitSuccess(options.onExitSuccess);
339
+ }
340
+ if (options?.onExitAbort) {
341
+ newConnector.onExitAbort(options.onExitAbort);
342
+ }
343
+ if (options?.onExitError) {
344
+ newConnector.onExitError(options.onExitError);
345
+ }
346
+ }, {
347
+ immediate: true
348
+ });
349
+ // Handle deferred opening
350
+ vue.watch([
351
+ connector,
352
+ isOpening
353
+ ], ([conn, opening])=>{
354
+ if (conn && opening) {
355
+ isOpening.value = false;
356
+ conn.open();
357
+ }
358
+ });
359
+ // Warn on unmount if connector is still open
360
+ vue.onUnmounted(()=>{
361
+ if (isConnectorOpen.value) {
362
+ console.error('[Quiltt] useQuilttConnector: Component unmounted while Connector is still open. ' + 'This may lead to memory leaks or unexpected behavior.');
363
+ }
364
+ });
365
+ /**
366
+ * Open the connector modal
367
+ */ const open = ()=>{
368
+ if (getConnectorId()) {
369
+ isOpening.value = true;
370
+ updateConnector();
371
+ } else {
372
+ throw new Error('Must provide connectorId to open Quiltt Connector');
373
+ }
374
+ };
375
+ return {
376
+ open
377
+ };
378
+ };
379
+
380
+ /**
381
+ * Search institutions for a connector.
382
+ * Requires QuilttPlugin session context and throws when used without it.
383
+ */ const useQuilttInstitutions = (connectorId, onErrorCallback)=>{
384
+ const { session } = useQuilttSession();
385
+ const searchTermInput = vue.ref('');
386
+ const searchTerm = vue.ref('');
387
+ const searchResults = vue.ref([]);
388
+ const isSearching = vue.ref(false);
389
+ const debounceTimer = vue.ref();
390
+ const abortController = vue.shallowRef();
391
+ const sdkVersion = utils.extractVersionNumber(version);
392
+ const connectorsAPI = new core.ConnectorsAPI(connectorId, getSDKAgent(sdkVersion));
393
+ const handleError = (message)=>{
394
+ const errorMessage = message || 'Unknown error occurred while searching institutions';
395
+ console.error('Quiltt Institutions Search Error:', errorMessage);
396
+ onErrorCallback?.(errorMessage);
397
+ };
398
+ const setSearchTerm = (term)=>{
399
+ searchTermInput.value = term;
400
+ if (debounceTimer.value) {
401
+ clearTimeout(debounceTimer.value);
402
+ }
403
+ if (term.trim().length < 2) {
404
+ abortController.value?.abort();
405
+ abortController.value = undefined;
406
+ searchTerm.value = '';
407
+ searchResults.value = [];
408
+ isSearching.value = false;
409
+ return;
410
+ }
411
+ isSearching.value = true;
412
+ debounceTimer.value = setTimeout(()=>{
413
+ searchTerm.value = term;
414
+ }, 350);
415
+ };
416
+ vue.watch([
417
+ ()=>session.value?.token,
418
+ searchTerm
419
+ ], async ([token, term])=>{
420
+ if (!token || !connectorId || !term || term.trim().length < 2) {
421
+ return;
422
+ }
423
+ abortController.value?.abort();
424
+ const controller = new AbortController();
425
+ abortController.value = controller;
426
+ try {
427
+ const response = await connectorsAPI.searchInstitutions(token, connectorId, term, controller.signal);
428
+ if (abortController.value !== controller || controller.signal.aborted) {
429
+ return;
430
+ }
431
+ if (response.status === 200) {
432
+ searchResults.value = response.data;
433
+ } else {
434
+ handleError(response.data.message || 'Failed to fetch institutions');
435
+ }
436
+ } catch (error) {
437
+ if (abortController.value === controller && !controller.signal.aborted) {
438
+ handleError(error?.message);
439
+ }
440
+ } finally{
441
+ if (abortController.value === controller && !controller.signal.aborted) {
442
+ isSearching.value = false;
443
+ }
444
+ }
445
+ }, {
446
+ immediate: false
447
+ });
448
+ vue.onUnmounted(()=>{
449
+ if (debounceTimer.value) {
450
+ clearTimeout(debounceTimer.value);
451
+ }
452
+ abortController.value?.abort();
453
+ });
454
+ return {
455
+ searchTerm: vue.computed(()=>searchTerm.value),
456
+ searchResults: vue.computed(()=>searchResults.value),
457
+ isSearching: vue.computed(()=>isSearching.value),
458
+ setSearchTerm
459
+ };
460
+ };
461
+
462
+ /**
463
+ * Check whether a provider link is resolvable for a connector.
464
+ * Requires QuilttPlugin session context and throws when used without it.
465
+ */ const useQuilttResolvable = (connectorId, onErrorCallback)=>{
466
+ const { session } = useQuilttSession();
467
+ const sdkVersion = utils.extractVersionNumber(version);
468
+ const connectorsAPI = new core.ConnectorsAPI(connectorId, getSDKAgent(sdkVersion));
469
+ const isLoading = vue.ref(false);
470
+ const isResolvable = vue.ref(null);
471
+ const error = vue.ref(null);
472
+ const handleError = (message)=>{
473
+ const errorMessage = message || 'Unknown error occurred while checking resolvability';
474
+ error.value = errorMessage;
475
+ console.error('Quiltt Connector Resolvable Error:', errorMessage);
476
+ onErrorCallback?.(errorMessage);
477
+ };
478
+ const checkResolvable = async (providerId)=>{
479
+ if (!session.value?.token) {
480
+ handleError('Missing session token');
481
+ return null;
482
+ }
483
+ if (!connectorId) {
484
+ handleError('Missing connector ID');
485
+ return null;
486
+ }
487
+ const hasProviderId = Object.values(providerId).some((id)=>!!id);
488
+ if (!hasProviderId) {
489
+ handleError('No provider ID specified');
490
+ return null;
491
+ }
492
+ isLoading.value = true;
493
+ error.value = null;
494
+ try {
495
+ const response = await connectorsAPI.checkResolvable(session.value.token, connectorId, providerId);
496
+ if (response.status === 200) {
497
+ const result = response.data.resolvable;
498
+ isResolvable.value = result;
499
+ return result;
500
+ }
501
+ handleError(response.data.message || 'Failed to check resolvability');
502
+ isResolvable.value = null;
503
+ return null;
504
+ } catch (caught) {
505
+ handleError(caught?.message);
506
+ isResolvable.value = null;
507
+ return null;
508
+ } finally{
509
+ isLoading.value = false;
510
+ }
511
+ };
512
+ return {
513
+ checkResolvable,
514
+ isLoading: vue.computed(()=>isLoading.value),
515
+ isResolvable: vue.computed(()=>isResolvable.value),
516
+ error: vue.computed(()=>error.value)
517
+ };
518
+ };
519
+
520
+ /**
521
+ * Read plugin-provided Quiltt settings.
522
+ * When used without QuilttPlugin context, values are undefined.
523
+ */ const useQuilttSettings = ()=>{
524
+ const clientIdRef = vue.inject(QuilttClientIdKey);
525
+ return {
526
+ clientId: vue.computed(()=>clientIdRef?.value)
527
+ };
528
+ };
529
+
530
+ const useStorage = (key, initialState)=>{
531
+ const readStorage = ()=>{
532
+ const current = core.GlobalStorage.get(key);
533
+ if (current !== undefined) {
534
+ return current;
535
+ }
536
+ return initialState;
537
+ };
538
+ const state = vue.ref(readStorage());
539
+ const setStorage = (nextState)=>{
540
+ const resolved = nextState instanceof Function ? nextState(state.value) : nextState;
541
+ if (state.value !== resolved) {
542
+ core.GlobalStorage.set(key, resolved);
543
+ state.value = resolved;
544
+ }
545
+ };
546
+ const handleStorageChange = (newValue)=>{
547
+ state.value = newValue;
548
+ };
549
+ core.GlobalStorage.subscribe(key, handleStorageChange);
550
+ // Use onScopeDispose for cleanup in any effect scope (components, effectScope(), etc.)
551
+ // This safely handles cases where composable is called outside a scope
552
+ if (vue.getCurrentScope()) {
553
+ vue.onScopeDispose(()=>{
554
+ core.GlobalStorage.unsubscribe(key, handleStorageChange);
555
+ });
556
+ }
557
+ return {
558
+ storage: vue.computed(()=>state.value),
559
+ setStorage
560
+ };
561
+ };
562
+
563
+ const parse = core.JsonWebTokenParse;
564
+ const sessionTimer = new core.Timeoutable();
565
+ const useSession = (storageKey = 'session')=>{
566
+ const { storage: token, setStorage } = useStorage(storageKey);
567
+ const session = vue.ref(parse(token.value));
568
+ let currentExpire = null;
569
+ vue.watch(token, (newToken)=>{
570
+ session.value = parse(newToken);
571
+ }, {
572
+ immediate: true
573
+ });
574
+ vue.watch(()=>session.value, (nextSession)=>{
575
+ if (currentExpire) {
576
+ sessionTimer.clear(currentExpire);
577
+ currentExpire = null;
578
+ }
579
+ if (!nextSession) {
580
+ return;
581
+ }
582
+ const expirationMS = nextSession.claims.exp * 1000;
583
+ const expire = ()=>setStorage(null);
584
+ currentExpire = expire;
585
+ if (Date.now() >= expirationMS) {
586
+ expire();
587
+ return;
588
+ }
589
+ sessionTimer.set(expire, expirationMS - Date.now());
590
+ }, {
591
+ immediate: true
592
+ });
593
+ const setSession = (nextState)=>{
594
+ const resolved = nextState instanceof Function ? nextState(token.value) : nextState;
595
+ if (token.value !== resolved && (!resolved || parse(resolved))) {
596
+ setStorage(resolved);
597
+ }
598
+ };
599
+ vue.onUnmounted(()=>{
600
+ if (currentExpire) {
601
+ sessionTimer.clear(currentExpire);
602
+ currentExpire = null;
603
+ }
604
+ });
605
+ return {
606
+ session: vue.computed(()=>session.value),
607
+ setSession
608
+ };
609
+ };
610
+
611
+ exports.useQuilttConnector = useQuilttConnector;
612
+ exports.useQuilttInstitutions = useQuilttInstitutions;
613
+ exports.useQuilttResolvable = useQuilttResolvable;
614
+ exports.useQuilttSession = useQuilttSession;
615
+ exports.useQuilttSettings = useQuilttSettings;
616
+ exports.useSession = useSession;
617
+ exports.useStorage = useStorage;