@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,707 @@
|
|
|
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 = 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(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
|
+
const QuilttButton = vue.defineComponent({
|
|
381
|
+
name: 'QuilttButton',
|
|
382
|
+
props: {
|
|
383
|
+
/** Quiltt Connector ID */ connectorId: {
|
|
384
|
+
type: String,
|
|
385
|
+
required: true
|
|
386
|
+
},
|
|
387
|
+
/** Existing connection ID for reconnection */ connectionId: {
|
|
388
|
+
type: String,
|
|
389
|
+
default: undefined
|
|
390
|
+
},
|
|
391
|
+
/** Pre-select a specific institution */ institution: {
|
|
392
|
+
type: String,
|
|
393
|
+
default: undefined
|
|
394
|
+
},
|
|
395
|
+
/** Deep link URL for OAuth callbacks (mobile apps) */ appLauncherUrl: {
|
|
396
|
+
type: String,
|
|
397
|
+
default: undefined
|
|
398
|
+
},
|
|
399
|
+
/**
|
|
400
|
+
* @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
|
|
401
|
+
* The OAuth redirect URL for mobile or embedded webview flows.
|
|
402
|
+
*/ oauthRedirectUrl: {
|
|
403
|
+
type: String,
|
|
404
|
+
default: undefined
|
|
405
|
+
},
|
|
406
|
+
/** Render as a different element */ as: {
|
|
407
|
+
type: String,
|
|
408
|
+
default: 'button'
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
emits: {
|
|
412
|
+
/** Connector loaded */ load: (_metadata)=>true,
|
|
413
|
+
/** Connector opened */ open: (_metadata)=>true,
|
|
414
|
+
/** Connection successful */ 'exit-success': (_metadata)=>true,
|
|
415
|
+
/** User cancelled */ 'exit-abort': (_metadata)=>true,
|
|
416
|
+
/** Error occurred */ 'exit-error': (_metadata)=>true,
|
|
417
|
+
/** Connector exited (any reason) */ exit: (_type, _metadata)=>true,
|
|
418
|
+
/** Any connector event */ event: (_type, _metadata)=>true
|
|
419
|
+
},
|
|
420
|
+
setup (props, { emit, slots }) {
|
|
421
|
+
vue.watch(()=>props.oauthRedirectUrl, (value)=>{
|
|
422
|
+
if (value !== undefined) {
|
|
423
|
+
console.warn(oauthRedirectUrlDeprecationWarning);
|
|
424
|
+
}
|
|
425
|
+
}, {
|
|
426
|
+
immediate: true
|
|
427
|
+
});
|
|
428
|
+
const effectiveAppLauncherUri = vue.computed(()=>props.appLauncherUrl ?? props.oauthRedirectUrl);
|
|
429
|
+
const { open } = useQuilttConnector(()=>props.connectorId, {
|
|
430
|
+
connectionId: ()=>props.connectionId,
|
|
431
|
+
institution: ()=>props.institution,
|
|
432
|
+
appLauncherUrl: effectiveAppLauncherUri,
|
|
433
|
+
onEvent: (type, metadata)=>emit('event', type, metadata),
|
|
434
|
+
onOpen: (metadata)=>emit('open', metadata),
|
|
435
|
+
onLoad: (metadata)=>emit('load', metadata),
|
|
436
|
+
onExit: (type, metadata)=>emit('exit', type, metadata),
|
|
437
|
+
onExitSuccess: (metadata)=>emit('exit-success', metadata),
|
|
438
|
+
onExitAbort: (metadata)=>emit('exit-abort', metadata),
|
|
439
|
+
onExitError: (metadata)=>emit('exit-error', metadata)
|
|
440
|
+
});
|
|
441
|
+
const handleClick = ()=>{
|
|
442
|
+
open();
|
|
443
|
+
};
|
|
444
|
+
return ()=>vue.h(props.as, {
|
|
445
|
+
class: 'quiltt-button',
|
|
446
|
+
onClick: handleClick
|
|
447
|
+
}, slots.default?.());
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const QuilttConnector = vue.defineComponent({
|
|
452
|
+
name: 'QuilttConnector',
|
|
453
|
+
props: {
|
|
454
|
+
/** Quiltt Connector ID */ connectorId: {
|
|
455
|
+
type: String,
|
|
456
|
+
required: true
|
|
457
|
+
},
|
|
458
|
+
/** Existing connection ID for reconnection */ connectionId: {
|
|
459
|
+
type: String,
|
|
460
|
+
default: undefined
|
|
461
|
+
},
|
|
462
|
+
/** Pre-select a specific institution */ institution: {
|
|
463
|
+
type: String,
|
|
464
|
+
default: undefined
|
|
465
|
+
},
|
|
466
|
+
/** Deep link URL for OAuth callbacks (mobile apps) */ appLauncherUrl: {
|
|
467
|
+
type: String,
|
|
468
|
+
default: undefined
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
emits: {
|
|
472
|
+
/** Connector loaded */ load: (_metadata)=>true,
|
|
473
|
+
/** Connection successful */ 'exit-success': (_metadata)=>true,
|
|
474
|
+
/** User cancelled */ 'exit-abort': (_metadata)=>true,
|
|
475
|
+
/** Error occurred */ 'exit-error': (_metadata)=>true,
|
|
476
|
+
/** Any connector event */ event: (_type, _metadata)=>true,
|
|
477
|
+
/** OAuth URL requested (for native handling) */ navigate: (_url)=>true
|
|
478
|
+
},
|
|
479
|
+
setup (props, { emit, expose }) {
|
|
480
|
+
const iframeRef = vue.ref();
|
|
481
|
+
const { session } = useQuilttSession();
|
|
482
|
+
const trustedQuilttHostSuffixes = [
|
|
483
|
+
'quiltt.io',
|
|
484
|
+
'quiltt.dev',
|
|
485
|
+
'quiltt.app'
|
|
486
|
+
];
|
|
487
|
+
const isTrustedQuilttOrigin = (origin)=>{
|
|
488
|
+
try {
|
|
489
|
+
const originUrl = new URL(origin);
|
|
490
|
+
if (originUrl.protocol !== 'https:') {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
const hostname = originUrl.hostname.toLowerCase();
|
|
494
|
+
return trustedQuilttHostSuffixes.some((suffix)=>hostname === suffix || hostname.endsWith(`.${suffix}`));
|
|
495
|
+
} catch {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
// Connector origin for secure postMessage targeting
|
|
500
|
+
const connectorOrigin = vue.computed(()=>`https://${props.connectorId}.quiltt.app`);
|
|
501
|
+
// Build connector URL
|
|
502
|
+
const connectorUrl = vue.computed(()=>{
|
|
503
|
+
const url = new URL(connectorOrigin.value);
|
|
504
|
+
if (session.value?.token) {
|
|
505
|
+
url.searchParams.set('token', session.value.token);
|
|
506
|
+
}
|
|
507
|
+
if (props.connectionId) {
|
|
508
|
+
url.searchParams.set('connectionId', props.connectionId);
|
|
509
|
+
}
|
|
510
|
+
if (props.institution) {
|
|
511
|
+
url.searchParams.set('institution', props.institution);
|
|
512
|
+
}
|
|
513
|
+
if (props.appLauncherUrl) {
|
|
514
|
+
url.searchParams.set('app_launcher_url', props.appLauncherUrl);
|
|
515
|
+
}
|
|
516
|
+
if (typeof window !== 'undefined') {
|
|
517
|
+
url.searchParams.set('embed_location', window.location.href);
|
|
518
|
+
}
|
|
519
|
+
// Set mode for inline iframe embedding
|
|
520
|
+
url.searchParams.set('mode', 'INLINE');
|
|
521
|
+
return url.toString();
|
|
522
|
+
});
|
|
523
|
+
// Handle messages from the iframe
|
|
524
|
+
// The platform MessageBus sends: { source: 'quiltt', type: 'Load'|'ExitSuccess'|..., ...metadata }
|
|
525
|
+
const handleMessage = (event)=>{
|
|
526
|
+
if (!isTrustedQuilttOrigin(event.origin)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const data = event.data || {};
|
|
530
|
+
// Validate message is from Quiltt MessageBus
|
|
531
|
+
if (data.source !== 'quiltt' || !data.type) return;
|
|
532
|
+
const { type, connectionId, profileId, connectorSession, url } = data;
|
|
533
|
+
// Build metadata from message fields
|
|
534
|
+
const metadata = {
|
|
535
|
+
connectorId: props.connectorId,
|
|
536
|
+
...profileId && {
|
|
537
|
+
profileId
|
|
538
|
+
},
|
|
539
|
+
...connectionId && {
|
|
540
|
+
connectionId
|
|
541
|
+
},
|
|
542
|
+
...connectorSession && {
|
|
543
|
+
connectorSession
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
switch(type){
|
|
547
|
+
case 'Load':
|
|
548
|
+
emit('event', 'Load', metadata);
|
|
549
|
+
emit('load', metadata);
|
|
550
|
+
break;
|
|
551
|
+
case 'ExitSuccess':
|
|
552
|
+
emit('event', 'ExitSuccess', metadata);
|
|
553
|
+
emit('exit-success', metadata);
|
|
554
|
+
break;
|
|
555
|
+
case 'ExitAbort':
|
|
556
|
+
emit('event', 'ExitAbort', metadata);
|
|
557
|
+
emit('exit-abort', metadata);
|
|
558
|
+
break;
|
|
559
|
+
case 'ExitError':
|
|
560
|
+
emit('event', 'ExitError', metadata);
|
|
561
|
+
emit('exit-error', metadata);
|
|
562
|
+
break;
|
|
563
|
+
case 'Navigate':
|
|
564
|
+
if (url) {
|
|
565
|
+
emit('navigate', url);
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
// Build OAuth callback message matching React Native SDK format
|
|
571
|
+
const buildOAuthCallbackMessage = (callbackUrl)=>{
|
|
572
|
+
try {
|
|
573
|
+
const parsedUrl = new URL(callbackUrl);
|
|
574
|
+
const params = {};
|
|
575
|
+
parsedUrl.searchParams.forEach((value, key)=>{
|
|
576
|
+
params[key] = value;
|
|
577
|
+
});
|
|
578
|
+
return {
|
|
579
|
+
source: 'quiltt',
|
|
580
|
+
type: 'OAuthCallback',
|
|
581
|
+
data: {
|
|
582
|
+
url: callbackUrl,
|
|
583
|
+
params
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
} catch {
|
|
587
|
+
return {
|
|
588
|
+
source: 'quiltt',
|
|
589
|
+
type: 'OAuthCallback',
|
|
590
|
+
data: {
|
|
591
|
+
url: callbackUrl,
|
|
592
|
+
params: {}
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const handleOAuthCallback = (url)=>{
|
|
598
|
+
iframeRef.value?.contentWindow?.postMessage(buildOAuthCallbackMessage(url), connectorOrigin.value);
|
|
599
|
+
};
|
|
600
|
+
expose({
|
|
601
|
+
handleOAuthCallback
|
|
602
|
+
});
|
|
603
|
+
vue.onMounted(()=>{
|
|
604
|
+
window.addEventListener('message', handleMessage);
|
|
605
|
+
});
|
|
606
|
+
vue.onUnmounted(()=>{
|
|
607
|
+
window.removeEventListener('message', handleMessage);
|
|
608
|
+
});
|
|
609
|
+
return ()=>vue.h('iframe', {
|
|
610
|
+
ref: iframeRef,
|
|
611
|
+
src: connectorUrl.value,
|
|
612
|
+
allow: 'publickey-credentials-get *',
|
|
613
|
+
class: 'quiltt-connector',
|
|
614
|
+
style: {
|
|
615
|
+
border: 'none',
|
|
616
|
+
width: '100%',
|
|
617
|
+
height: '100%'
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const QuilttContainer = vue.defineComponent({
|
|
624
|
+
name: 'QuilttContainer',
|
|
625
|
+
props: {
|
|
626
|
+
/** Quiltt Connector ID */ connectorId: {
|
|
627
|
+
type: String,
|
|
628
|
+
required: true
|
|
629
|
+
},
|
|
630
|
+
/** Existing connection ID for reconnection */ connectionId: {
|
|
631
|
+
type: String,
|
|
632
|
+
default: undefined
|
|
633
|
+
},
|
|
634
|
+
/** Pre-select a specific institution */ institution: {
|
|
635
|
+
type: String,
|
|
636
|
+
default: undefined
|
|
637
|
+
},
|
|
638
|
+
/** Deep link URL for OAuth callbacks (mobile apps) */ appLauncherUrl: {
|
|
639
|
+
type: String,
|
|
640
|
+
default: undefined
|
|
641
|
+
},
|
|
642
|
+
/**
|
|
643
|
+
* @deprecated Use `appLauncherUrl` instead. This property will be removed in a future version.
|
|
644
|
+
* The OAuth redirect URL for mobile or embedded webview flows.
|
|
645
|
+
*/ oauthRedirectUrl: {
|
|
646
|
+
type: String,
|
|
647
|
+
default: undefined
|
|
648
|
+
},
|
|
649
|
+
/** Render as a different element */ as: {
|
|
650
|
+
type: String,
|
|
651
|
+
default: 'div'
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
emits: {
|
|
655
|
+
/** Connector loaded */ load: (_metadata)=>true,
|
|
656
|
+
/** Connection successful */ 'exit-success': (_metadata)=>true,
|
|
657
|
+
/** User cancelled */ 'exit-abort': (_metadata)=>true,
|
|
658
|
+
/** Error occurred */ 'exit-error': (_metadata)=>true,
|
|
659
|
+
/** Connector exited (any reason) */ exit: (_type, _metadata)=>true,
|
|
660
|
+
/** Any connector event */ event: (_type, _metadata)=>true
|
|
661
|
+
},
|
|
662
|
+
setup (props, { emit, slots }) {
|
|
663
|
+
vue.watch(()=>props.oauthRedirectUrl, (value)=>{
|
|
664
|
+
if (value !== undefined) {
|
|
665
|
+
console.warn(oauthRedirectUrlDeprecationWarning);
|
|
666
|
+
}
|
|
667
|
+
}, {
|
|
668
|
+
immediate: true
|
|
669
|
+
});
|
|
670
|
+
const effectiveAppLauncherUri = vue.computed(()=>props.appLauncherUrl ?? props.oauthRedirectUrl);
|
|
671
|
+
let openTimeout;
|
|
672
|
+
const { open } = useQuilttConnector(()=>props.connectorId, {
|
|
673
|
+
connectionId: ()=>props.connectionId,
|
|
674
|
+
institution: ()=>props.institution,
|
|
675
|
+
appLauncherUrl: effectiveAppLauncherUri,
|
|
676
|
+
onEvent: (type, metadata)=>emit('event', type, metadata),
|
|
677
|
+
onLoad: (metadata)=>emit('load', metadata),
|
|
678
|
+
onExit: (type, metadata)=>emit('exit', type, metadata),
|
|
679
|
+
onExitSuccess: (metadata)=>emit('exit-success', metadata),
|
|
680
|
+
onExitAbort: (metadata)=>emit('exit-abort', metadata),
|
|
681
|
+
onExitError: (metadata)=>emit('exit-error', metadata)
|
|
682
|
+
});
|
|
683
|
+
vue.onMounted(()=>{
|
|
684
|
+
// Short delay to ensure SDK is loaded
|
|
685
|
+
openTimeout = setTimeout(()=>{
|
|
686
|
+
open();
|
|
687
|
+
}, 100);
|
|
688
|
+
});
|
|
689
|
+
vue.onUnmounted(()=>{
|
|
690
|
+
if (openTimeout) {
|
|
691
|
+
clearTimeout(openTimeout);
|
|
692
|
+
openTimeout = undefined;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
return ()=>vue.h(props.as, {
|
|
696
|
+
class: 'quiltt-container',
|
|
697
|
+
style: {
|
|
698
|
+
width: '100%',
|
|
699
|
+
height: '100%'
|
|
700
|
+
}
|
|
701
|
+
}, slots.default?.());
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
exports.QuilttButton = QuilttButton;
|
|
706
|
+
exports.QuilttConnector = QuilttConnector;
|
|
707
|
+
exports.QuilttContainer = QuilttContainer;
|