@embarkai/ui-kit 0.1.1
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 +21 -0
- package/README.md +977 -0
- package/dist/iframe/_headers +49 -0
- package/dist/iframe/dkls23_wasm_bg.wasm +0 -0
- package/dist/iframe/index.html +881 -0
- package/dist/iframe/kyc/sumsub.html +102 -0
- package/dist/iframe/kyc/sumsub.js +237 -0
- package/dist/iframe/lumia-logo.svg +1 -0
- package/dist/iframe/main.js +5180 -0
- package/dist/iframe/main.js.map +1 -0
- package/dist/iframe/oauth/telegram.html +129 -0
- package/dist/iframe/oauth/telegram.js +112 -0
- package/dist/iframe/oauth/x.html +162 -0
- package/dist/iframe/oauth/x.js +436 -0
- package/dist/index.cjs +21115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2719 -0
- package/dist/index.d.ts +2719 -0
- package/dist/index.js +20978 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X (Twitter) OAuth Popup Script
|
|
3
|
+
* Handles OAuth 2.0 with PKCE flow for X authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Get configuration from URL parameters
|
|
7
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
8
|
+
|
|
9
|
+
// TSS_URL and PROJECT_ID are build-time constants, passed via URL on initial load
|
|
10
|
+
// After backend redirect, we use stored values from localStorage
|
|
11
|
+
const TSS_URL = urlParams.get('tssUrl') || localStorage.getItem('x_oauth_tssUrl') || (typeof __LUMIA_TSS_URL__ !== 'undefined' ? __LUMIA_TSS_URL__ : null);
|
|
12
|
+
const MODE = urlParams.get('mode') || localStorage.getItem('x_oauth_mode') || 'login';
|
|
13
|
+
const PROJECT_ID = urlParams.get('projectId') || localStorage.getItem('x_oauth_projectId') || (typeof window !== 'undefined' && window.__LUMIA_PROJECT_ID__) || null;
|
|
14
|
+
|
|
15
|
+
const STATE = urlParams.get('state');
|
|
16
|
+
const CODE = urlParams.get('code');
|
|
17
|
+
const ERROR_PARAM = urlParams.get('error');
|
|
18
|
+
const SUCCESS = urlParams.get('success'); // Backend redirected with success=true
|
|
19
|
+
|
|
20
|
+
console.log('[X OAuth] Initializing with:', { TSS_URL, MODE, PROJECT_ID, STATE, CODE, ERROR_PARAM, SUCCESS });
|
|
21
|
+
|
|
22
|
+
const contentEl = document.getElementById('content');
|
|
23
|
+
|
|
24
|
+
// Track if we've successfully sent auth result to parent
|
|
25
|
+
let authResultSent = false;
|
|
26
|
+
// Track if we're redirecting to X OAuth (to prevent cancellation on redirect)
|
|
27
|
+
let isRedirectingToProvider = false;
|
|
28
|
+
|
|
29
|
+
function showLoading(message) {
|
|
30
|
+
if (contentEl) {
|
|
31
|
+
contentEl.innerHTML = `
|
|
32
|
+
<div class="loading">
|
|
33
|
+
<div class="spinner"></div>
|
|
34
|
+
<p>${message}</p>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function showError(message, allowRetry = true) {
|
|
41
|
+
console.error('[X OAuth] Error:', message);
|
|
42
|
+
|
|
43
|
+
if (contentEl) {
|
|
44
|
+
contentEl.innerHTML = `
|
|
45
|
+
<div class="error">
|
|
46
|
+
<strong>Error:</strong> ${message}
|
|
47
|
+
</div>
|
|
48
|
+
${allowRetry ? '<button class="retry-button" onclick="window.location.reload()">Retry</button>' : ''}
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Send error to opener
|
|
53
|
+
if (window.opener) {
|
|
54
|
+
window.opener.postMessage({
|
|
55
|
+
type: 'X_AUTH_ERROR',
|
|
56
|
+
provider: 'x',
|
|
57
|
+
error: message
|
|
58
|
+
}, '*');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showSuccess(message) {
|
|
63
|
+
if (contentEl) {
|
|
64
|
+
contentEl.innerHTML = `
|
|
65
|
+
<div class="success">
|
|
66
|
+
✓ ${message}<br>
|
|
67
|
+
<small style="color: #666;">Closing window...</small>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function startOAuthFlow() {
|
|
74
|
+
if (!TSS_URL) {
|
|
75
|
+
showError('TSS URL not configured. Missing tssUrl parameter.', false);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
showLoading('Starting X OAuth flow...');
|
|
81
|
+
|
|
82
|
+
// Determine the correct endpoint based on mode
|
|
83
|
+
const endpoint = MODE === 'link'
|
|
84
|
+
? `${TSS_URL}/api/auth/link/x/start`
|
|
85
|
+
: `${TSS_URL}/api/auth/x/start`;
|
|
86
|
+
|
|
87
|
+
// Add projectId to endpoint if available
|
|
88
|
+
const fullEndpoint = PROJECT_ID
|
|
89
|
+
? `${endpoint}?projectId=${encodeURIComponent(PROJECT_ID)}`
|
|
90
|
+
: endpoint;
|
|
91
|
+
|
|
92
|
+
console.log('[X OAuth] Starting flow with config:', { TSS_URL, MODE, PROJECT_ID, endpoint: fullEndpoint });
|
|
93
|
+
console.log('[X OAuth] Making request to:', fullEndpoint);
|
|
94
|
+
|
|
95
|
+
// Call backend to get authorization URL
|
|
96
|
+
const response = await fetch(fullEndpoint, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
...(MODE === 'link' ? {
|
|
101
|
+
'Authorization': `Bearer ${urlParams.get('token')}`
|
|
102
|
+
} : {})
|
|
103
|
+
},
|
|
104
|
+
credentials: 'include'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const errorText = await response.text().catch(() => '');
|
|
109
|
+
let errorData;
|
|
110
|
+
try {
|
|
111
|
+
errorData = JSON.parse(errorText);
|
|
112
|
+
} catch {
|
|
113
|
+
errorData = { message: errorText };
|
|
114
|
+
}
|
|
115
|
+
console.error('[X OAuth] Backend error response:', { status: response.status, errorData, errorText });
|
|
116
|
+
throw new Error(errorData.message || errorText || `Failed to start OAuth: ${response.statusText}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
console.log('[X OAuth] Backend response:', data);
|
|
121
|
+
|
|
122
|
+
// Backend may return 'url' or 'authorizationUrl'
|
|
123
|
+
const authUrl = data.authorizationUrl || data.url;
|
|
124
|
+
console.log('[X OAuth] Authorization URL:', authUrl);
|
|
125
|
+
|
|
126
|
+
if (!authUrl) {
|
|
127
|
+
throw new Error('No authorization URL returned from server');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Store state and config in localStorage (persists across redirects)
|
|
131
|
+
if (data.state) {
|
|
132
|
+
localStorage.setItem('x_oauth_state', data.state);
|
|
133
|
+
}
|
|
134
|
+
localStorage.setItem('x_oauth_mode', MODE);
|
|
135
|
+
localStorage.setItem('x_oauth_tssUrl', TSS_URL);
|
|
136
|
+
if (PROJECT_ID) {
|
|
137
|
+
localStorage.setItem('x_oauth_projectId', PROJECT_ID);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Redirect to X OAuth page
|
|
141
|
+
showLoading('Redirecting to X...');
|
|
142
|
+
isRedirectingToProvider = true; // Prevent cancellation message on redirect
|
|
143
|
+
window.location.href = authUrl;
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('[X OAuth] Start flow error:', error);
|
|
147
|
+
const errorMessage = error.message || 'Failed to start OAuth flow';
|
|
148
|
+
console.error('[X OAuth] Error details:', { error, MODE, TSS_URL, PROJECT_ID });
|
|
149
|
+
showError(errorMessage);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle successful OAuth after backend redirect
|
|
155
|
+
* Backend processes callback and redirects back with success=true
|
|
156
|
+
* If authCode is present, exchanges it for tokens (Safari ITP fix)
|
|
157
|
+
* Otherwise falls back to cookie-based verify (desktop browsers)
|
|
158
|
+
*/
|
|
159
|
+
async function handleBackendSuccess() {
|
|
160
|
+
try {
|
|
161
|
+
showLoading('Completing authentication...');
|
|
162
|
+
|
|
163
|
+
console.log('[X OAuth] Backend redirected with success, verifying session...');
|
|
164
|
+
console.log('[X OAuth] Using config:', { TSS_URL, PROJECT_ID, MODE });
|
|
165
|
+
|
|
166
|
+
// Validate required parameters
|
|
167
|
+
if (!TSS_URL) {
|
|
168
|
+
throw new Error('Missing TSS URL. Check build-time configuration.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check for auth code (new flow for Safari ITP compatibility)
|
|
172
|
+
const authCode = urlParams.get('authCode');
|
|
173
|
+
let userData;
|
|
174
|
+
let tokens = null;
|
|
175
|
+
|
|
176
|
+
if (authCode) {
|
|
177
|
+
// New flow: Exchange auth code for tokens (fixes Safari ITP issues)
|
|
178
|
+
console.log('[X OAuth] Auth code present, exchanging for tokens...');
|
|
179
|
+
|
|
180
|
+
const exchangeEndpoint = PROJECT_ID
|
|
181
|
+
? `${TSS_URL}/api/auth/exchange-code?projectId=${encodeURIComponent(PROJECT_ID)}`
|
|
182
|
+
: `${TSS_URL}/api/auth/exchange-code`;
|
|
183
|
+
|
|
184
|
+
const exchangeResponse = await fetch(exchangeEndpoint, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
headers: { 'Content-Type': 'application/json' },
|
|
187
|
+
body: JSON.stringify({ code: authCode })
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!exchangeResponse.ok) {
|
|
191
|
+
const errorData = await exchangeResponse.json().catch(() => ({}));
|
|
192
|
+
console.error('[X OAuth] Code exchange failed:', exchangeResponse.status, errorData);
|
|
193
|
+
throw new Error(errorData.error || 'Failed to exchange auth code');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const exchangeData = await exchangeResponse.json();
|
|
197
|
+
console.log('[X OAuth] Code exchange successful:', { userId: exchangeData.userId, hasKeyshare: exchangeData.hasKeyshare });
|
|
198
|
+
|
|
199
|
+
// Extract tokens for postMessage
|
|
200
|
+
tokens = {
|
|
201
|
+
accessToken: exchangeData.accessToken,
|
|
202
|
+
refreshToken: exchangeData.refreshToken
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Build userData in the same format as verify endpoint
|
|
206
|
+
userData = {
|
|
207
|
+
valid: true,
|
|
208
|
+
userId: exchangeData.userId,
|
|
209
|
+
sessionId: exchangeData.sessionId,
|
|
210
|
+
expiresIn: exchangeData.expiresIn,
|
|
211
|
+
hasKeyshare: exchangeData.hasKeyshare,
|
|
212
|
+
displayName: exchangeData.displayName,
|
|
213
|
+
providers: exchangeData.providers
|
|
214
|
+
};
|
|
215
|
+
} else {
|
|
216
|
+
// Legacy flow: Use cookies (works on desktop browsers)
|
|
217
|
+
console.log('[X OAuth] No auth code, falling back to cookie-based verify...');
|
|
218
|
+
|
|
219
|
+
const verifyEndpoint = PROJECT_ID
|
|
220
|
+
? `${TSS_URL}/api/auth/verify?projectId=${encodeURIComponent(PROJECT_ID)}`
|
|
221
|
+
: `${TSS_URL}/api/auth/verify`;
|
|
222
|
+
|
|
223
|
+
const verifyResponse = await fetch(verifyEndpoint, {
|
|
224
|
+
method: 'GET',
|
|
225
|
+
credentials: 'include',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (!verifyResponse.ok) {
|
|
229
|
+
console.error('[X OAuth] Verify failed:', verifyResponse.status);
|
|
230
|
+
throw new Error('Failed to verify authentication. Session may not be created.');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
userData = await verifyResponse.json();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log('[X OAuth] Authentication verified:', userData);
|
|
237
|
+
|
|
238
|
+
// Send success to opener
|
|
239
|
+
if (window.opener) {
|
|
240
|
+
const message = {
|
|
241
|
+
type: 'X_AUTH_SUCCESS',
|
|
242
|
+
provider: 'x',
|
|
243
|
+
user: userData,
|
|
244
|
+
mode: MODE
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Include tokens if we have them (new flow)
|
|
248
|
+
if (tokens) {
|
|
249
|
+
message.tokens = tokens;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
window.opener.postMessage(message, '*');
|
|
253
|
+
|
|
254
|
+
// Mark that we've sent the auth result
|
|
255
|
+
authResultSent = true;
|
|
256
|
+
|
|
257
|
+
showSuccess(MODE === 'link' ? 'Account linked successfully!' : 'Authentication successful!');
|
|
258
|
+
|
|
259
|
+
// Clean up localStorage
|
|
260
|
+
localStorage.removeItem('x_oauth_state');
|
|
261
|
+
localStorage.removeItem('x_oauth_mode');
|
|
262
|
+
localStorage.removeItem('x_oauth_tssUrl');
|
|
263
|
+
localStorage.removeItem('x_oauth_projectId');
|
|
264
|
+
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
window.close();
|
|
267
|
+
}, 1500);
|
|
268
|
+
} else {
|
|
269
|
+
console.error('[X OAuth] No opener window found');
|
|
270
|
+
showError('No opener window found. Please close this window manually.');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error('[X OAuth] Backend success handler error:', error);
|
|
275
|
+
showError(error.message || 'Failed to complete authentication');
|
|
276
|
+
|
|
277
|
+
// Clean up localStorage on error
|
|
278
|
+
localStorage.removeItem('x_oauth_state');
|
|
279
|
+
localStorage.removeItem('x_oauth_mode');
|
|
280
|
+
localStorage.removeItem('x_oauth_tssUrl');
|
|
281
|
+
localStorage.removeItem('x_oauth_projectId');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Handle OAuth callback (legacy, may not be used if backend handles redirect_uri)
|
|
287
|
+
*/
|
|
288
|
+
async function handleCallback() {
|
|
289
|
+
try {
|
|
290
|
+
showLoading('Completing authentication...');
|
|
291
|
+
|
|
292
|
+
// Verify state matches
|
|
293
|
+
const savedState = localStorage.getItem('x_oauth_state');
|
|
294
|
+
const savedMode = localStorage.getItem('x_oauth_mode');
|
|
295
|
+
|
|
296
|
+
if (savedState && STATE !== savedState) {
|
|
297
|
+
throw new Error('Invalid state parameter. Possible CSRF attack.');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (ERROR_PARAM === 'access_denied') {
|
|
301
|
+
console.warn('[X OAuth] User denied authorization during legacy callback');
|
|
302
|
+
redirectWithAccessDenied();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (ERROR_PARAM) {
|
|
307
|
+
throw new Error(`OAuth error: ${ERROR_PARAM}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!CODE) {
|
|
311
|
+
throw new Error('No authorization code received');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log('[X OAuth] Callback successful, mode:', savedMode);
|
|
315
|
+
|
|
316
|
+
// For login mode, the backend callback endpoint handles everything
|
|
317
|
+
// and redirects back with tokens. For link mode, we need to verify.
|
|
318
|
+
if (savedMode === 'login') {
|
|
319
|
+
// The callback endpoint should have set cookies/tokens
|
|
320
|
+
// Get user info to verify
|
|
321
|
+
const response = await fetch(`${TSS_URL}/api/auth/verify`, {
|
|
322
|
+
credentials: 'include'
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
throw new Error('Failed to verify authentication');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const userData = await response.json();
|
|
330
|
+
console.log('[X OAuth] User data:', userData);
|
|
331
|
+
|
|
332
|
+
// Send success to opener
|
|
333
|
+
if (window.opener) {
|
|
334
|
+
window.opener.postMessage({
|
|
335
|
+
type: 'X_AUTH_SUCCESS',
|
|
336
|
+
provider: 'x',
|
|
337
|
+
user: userData,
|
|
338
|
+
mode: 'login'
|
|
339
|
+
}, '*');
|
|
340
|
+
|
|
341
|
+
showSuccess('Authentication successful!');
|
|
342
|
+
|
|
343
|
+
setTimeout(() => {
|
|
344
|
+
window.close();
|
|
345
|
+
}, 1500);
|
|
346
|
+
} else {
|
|
347
|
+
showError('No opener window found');
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Link mode - backend has already linked the account
|
|
351
|
+
if (window.opener) {
|
|
352
|
+
window.opener.postMessage({
|
|
353
|
+
type: 'X_AUTH_SUCCESS',
|
|
354
|
+
provider: 'x',
|
|
355
|
+
mode: 'link'
|
|
356
|
+
}, '*');
|
|
357
|
+
|
|
358
|
+
showSuccess('Account linked successfully!');
|
|
359
|
+
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
window.close();
|
|
362
|
+
}, 1500);
|
|
363
|
+
} else {
|
|
364
|
+
showError('No opener window found');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('[X OAuth] Callback error:', error);
|
|
370
|
+
showError(error.message || 'Failed to complete authentication');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Determine if this is a callback or initial load
|
|
375
|
+
if (ERROR_PARAM) {
|
|
376
|
+
// Backend redirected with error
|
|
377
|
+
const errorMessage = decodeURIComponent(ERROR_PARAM);
|
|
378
|
+
console.error('[X OAuth] Backend returned error:', errorMessage);
|
|
379
|
+
showError(errorMessage);
|
|
380
|
+
|
|
381
|
+
// Send error to opener
|
|
382
|
+
if (window.opener) {
|
|
383
|
+
window.opener.postMessage({
|
|
384
|
+
type: 'X_AUTH_ERROR',
|
|
385
|
+
provider: 'x',
|
|
386
|
+
error: errorMessage
|
|
387
|
+
}, '*');
|
|
388
|
+
authResultSent = true; // Mark that we've sent result
|
|
389
|
+
}
|
|
390
|
+
} else if (SUCCESS === 'true') {
|
|
391
|
+
// Backend redirected back after successful OAuth
|
|
392
|
+
handleBackendSuccess();
|
|
393
|
+
} else if (CODE) {
|
|
394
|
+
// This is a callback from X (should not happen with backend redirect_uri)
|
|
395
|
+
console.warn('[X OAuth] Received code parameter, but backend should handle this');
|
|
396
|
+
handleCallback();
|
|
397
|
+
} else {
|
|
398
|
+
// This is initial load, start OAuth flow
|
|
399
|
+
if (document.readyState === 'loading') {
|
|
400
|
+
document.addEventListener('DOMContentLoaded', startOAuthFlow);
|
|
401
|
+
} else {
|
|
402
|
+
startOAuthFlow();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Handle popup closing without authentication
|
|
407
|
+
window.addEventListener('beforeunload', () => {
|
|
408
|
+
// Only send cancellation if we haven't sent success/error AND not redirecting to provider
|
|
409
|
+
if (!authResultSent && !isRedirectingToProvider && window.opener) {
|
|
410
|
+
console.log('[X OAuth] Window closing without auth result, sending cancellation');
|
|
411
|
+
window.opener.postMessage({
|
|
412
|
+
type: 'X_AUTH_CANCELLED',
|
|
413
|
+
provider: 'x'
|
|
414
|
+
}, '*');
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
function redirectWithAccessDenied() {
|
|
418
|
+
const storedState = localStorage.getItem('x_oauth_state');
|
|
419
|
+
const storedMode = localStorage.getItem('x_oauth_mode') || MODE || 'login';
|
|
420
|
+
|
|
421
|
+
localStorage.removeItem('x_oauth_state');
|
|
422
|
+
localStorage.removeItem('x_oauth_mode');
|
|
423
|
+
localStorage.removeItem('x_oauth_tssUrl');
|
|
424
|
+
localStorage.removeItem('x_oauth_projectId');
|
|
425
|
+
|
|
426
|
+
const params = new URLSearchParams();
|
|
427
|
+
params.set('success', 'false');
|
|
428
|
+
params.set('error', 'ACCESS_DENIED');
|
|
429
|
+
params.set('mode', storedMode);
|
|
430
|
+
if (storedState) {
|
|
431
|
+
params.set('state', storedState);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const redirectUrl = `${window.location.origin}${window.location.pathname}?${params.toString()}`;
|
|
435
|
+
window.location.replace(redirectUrl);
|
|
436
|
+
}
|