@glideidentity/web-client-sdk 4.4.8-beta.2 → 4.4.8
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/README.md +395 -714
- package/dist/browser/web-client-sdk.min.js +1 -1
- package/dist/core/phone-auth/client.js +195 -75
- package/dist/core/phone-auth/strategies/desktop.d.ts +9 -3
- package/dist/core/phone-auth/strategies/desktop.js +47 -4
- package/dist/core/phone-auth/strategies/link.d.ts +8 -2
- package/dist/core/phone-auth/strategies/link.js +142 -14
- package/dist/core/phone-auth/types.d.ts +1 -1
- package/dist/core/phone-auth/ui/modal.d.ts +4 -0
- package/dist/core/phone-auth/ui/modal.js +17 -6
- package/dist/core/version.js +1 -1
- package/dist/esm/core/phone-auth/client.js +195 -75
- package/dist/esm/core/phone-auth/strategies/desktop.d.ts +9 -3
- package/dist/esm/core/phone-auth/strategies/desktop.js +47 -4
- package/dist/esm/core/phone-auth/strategies/link.d.ts +8 -2
- package/dist/esm/core/phone-auth/strategies/link.js +142 -14
- package/dist/esm/core/phone-auth/types.d.ts +1 -1
- package/dist/esm/core/phone-auth/ui/modal.d.ts +4 -0
- package/dist/esm/core/phone-auth/ui/modal.js +17 -6
- package/dist/esm/core/version.js +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -3
- package/package.json +1 -1
|
@@ -17,8 +17,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.LinkHandler = void 0;
|
|
18
18
|
class LinkHandler {
|
|
19
19
|
constructor() {
|
|
20
|
-
this.
|
|
20
|
+
this.isPollingActive = false;
|
|
21
21
|
this.isCancelled = false;
|
|
22
|
+
this.isPollingInProgress = false;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Invoke link-based authentication
|
|
@@ -26,12 +27,20 @@ class LinkHandler {
|
|
|
26
27
|
*/
|
|
27
28
|
invoke(data, options) {
|
|
28
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
console.log('[Link Auth] 🔗 invoke() called with data:', JSON.stringify(data, null, 2));
|
|
31
|
+
console.log('[Link Auth] Options:', options ? JSON.stringify({
|
|
32
|
+
pollingInterval: options.pollingInterval,
|
|
33
|
+
maxPollingAttempts: options.maxPollingAttempts,
|
|
34
|
+
pollingEndpoint: options.pollingEndpoint
|
|
35
|
+
}) : 'none');
|
|
29
36
|
// Extract link data from prepare response
|
|
30
37
|
const linkData = data.data;
|
|
31
38
|
if (!linkData || !linkData.url) {
|
|
32
39
|
throw new Error('Invalid link data: missing URL');
|
|
33
40
|
}
|
|
34
41
|
const sessionKey = data.session.session_key;
|
|
42
|
+
console.log('[Link Auth] Session key:', sessionKey);
|
|
43
|
+
console.log('[Link Auth] Link URL:', linkData.url);
|
|
35
44
|
// Open authentication app without navigating away from current page
|
|
36
45
|
this.openAuthenticationLink(linkData.url);
|
|
37
46
|
// Notify that link was opened
|
|
@@ -61,42 +70,97 @@ class LinkHandler {
|
|
|
61
70
|
startPolling(sessionKey, linkData, options) {
|
|
62
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
63
72
|
const interval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || 2000; // Fixed 2 second interval
|
|
64
|
-
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) ||
|
|
73
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30; // 1 minute with 2s interval
|
|
65
74
|
let attempts = 0;
|
|
75
|
+
console.log('[Link Auth] 🚀 Starting polling:', {
|
|
76
|
+
sessionKey,
|
|
77
|
+
interval: `${interval}ms`,
|
|
78
|
+
maxAttempts,
|
|
79
|
+
linkDataAvailable: !!linkData
|
|
80
|
+
});
|
|
66
81
|
return new Promise((resolve, reject) => {
|
|
67
|
-
this.
|
|
82
|
+
this.isPollingActive = true;
|
|
83
|
+
this.pollingReject = reject; // Store reject function for cancel()
|
|
68
84
|
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
if (!this.
|
|
85
|
+
if (!this.isPollingActive) {
|
|
70
86
|
return; // Polling was stopped
|
|
71
87
|
}
|
|
88
|
+
// Skip if another poll is already in progress
|
|
89
|
+
if (this.isPollingInProgress) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let statusUrl = ''; // Declare at function scope for catch block access
|
|
72
93
|
try {
|
|
73
|
-
|
|
74
|
-
// Check max attempts
|
|
94
|
+
this.isPollingInProgress = true;
|
|
95
|
+
// Check max attempts before making the request
|
|
75
96
|
if (attempts >= maxAttempts) {
|
|
76
97
|
this.stopPolling();
|
|
77
98
|
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
78
99
|
options.onTimeout();
|
|
79
100
|
}
|
|
101
|
+
// Calculate actual timeout duration
|
|
102
|
+
const timeoutSeconds = Math.round((maxAttempts * interval) / 1000);
|
|
103
|
+
const timeoutMessage = timeoutSeconds >= 60
|
|
104
|
+
? `${Math.floor(timeoutSeconds / 60)} minute${Math.floor(timeoutSeconds / 60) > 1 ? 's' : ''}`
|
|
105
|
+
: `${timeoutSeconds} seconds`;
|
|
80
106
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
81
107
|
options.onStatusUpdate({
|
|
82
108
|
status: 'expired',
|
|
83
|
-
message:
|
|
109
|
+
message: `Authentication timeout after ${timeoutMessage}`
|
|
84
110
|
});
|
|
85
111
|
}
|
|
86
|
-
reject(new Error(
|
|
112
|
+
reject(new Error(`Authentication timeout after ${timeoutMessage}`));
|
|
87
113
|
return;
|
|
88
114
|
}
|
|
89
115
|
// Build public status endpoint URL
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
// Use the same priority logic as Desktop strategy:
|
|
117
|
+
// 1. options?.pollingEndpoint (already contains invoke options OR client config from client.ts)
|
|
118
|
+
// 2. Backend-provided status_url from linkData
|
|
119
|
+
// 3. Hardcoded fallback to API server
|
|
120
|
+
let endpoint = options === null || options === void 0 ? void 0 : options.pollingEndpoint;
|
|
121
|
+
let endpointSource = 'options';
|
|
122
|
+
if (!endpoint && linkData.status_url) {
|
|
123
|
+
endpoint = linkData.status_url;
|
|
124
|
+
endpointSource = 'backend';
|
|
125
|
+
}
|
|
126
|
+
console.log('[Link Auth] Polling endpoint selection:');
|
|
127
|
+
console.log(' - options?.pollingEndpoint:', options === null || options === void 0 ? void 0 : options.pollingEndpoint);
|
|
128
|
+
console.log(' - linkData.status_url:', linkData.status_url);
|
|
129
|
+
console.log(' - selected endpoint:', endpoint, 'from source:', endpointSource);
|
|
130
|
+
// Build the status URL based on endpoint format (same as Desktop)
|
|
131
|
+
if (endpoint && (endpoint.startsWith('http://') || endpoint.startsWith('https://'))) {
|
|
132
|
+
// Full URL provided
|
|
133
|
+
if (endpoint.includes('{{session_id}}')) {
|
|
134
|
+
statusUrl = endpoint.replace('{{session_id}}', sessionKey);
|
|
135
|
+
}
|
|
136
|
+
else if (!endpoint.includes(sessionKey)) {
|
|
137
|
+
// If it doesn't already contain the session ID, check if it's a base URL
|
|
138
|
+
const url = new URL(endpoint);
|
|
139
|
+
statusUrl = `${url.protocol}//${url.host}/public/public/status/${sessionKey}`;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
statusUrl = endpoint;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (endpoint && endpoint !== '') {
|
|
146
|
+
// Relative path provided (e.g. '/api/phone-auth/status')
|
|
147
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
148
|
+
if (endpoint.includes('{{session_id}}')) {
|
|
149
|
+
statusUrl = origin + endpoint.replace('{{session_id}}', sessionKey);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Append session ID to the provided endpoint
|
|
153
|
+
statusUrl = origin + endpoint + '/' + sessionKey;
|
|
154
|
+
}
|
|
95
155
|
}
|
|
96
156
|
else {
|
|
157
|
+
// No endpoint provided - use hardcoded fallback
|
|
97
158
|
statusUrl = `https://api.glideidentity.app/public/public/status/${sessionKey}`;
|
|
159
|
+
endpointSource = 'fallback';
|
|
98
160
|
}
|
|
161
|
+
console.log(`[Link Auth] Using ${endpointSource} endpoint: ${statusUrl}`);
|
|
99
162
|
// Poll public status endpoint - no authentication required
|
|
163
|
+
console.log(`[Link Auth] Polling status (attempt ${attempts}/${maxAttempts}): ${statusUrl}`);
|
|
100
164
|
const response = yield fetch(statusUrl, {
|
|
101
165
|
method: 'GET',
|
|
102
166
|
headers: {
|
|
@@ -104,12 +168,16 @@ class LinkHandler {
|
|
|
104
168
|
// No Authorization header needed for public endpoint
|
|
105
169
|
}
|
|
106
170
|
});
|
|
171
|
+
console.log(`[Link Auth] Poll response - Status: ${response.status}, OK: ${response.ok}`);
|
|
107
172
|
// Handle based on HTTP status code
|
|
108
173
|
if (response.status === 200) {
|
|
109
174
|
// Session is active (pending or completed)
|
|
110
175
|
const result = yield response.json();
|
|
176
|
+
console.log('[Link Auth] Poll response data:', JSON.stringify(result, null, 2));
|
|
111
177
|
if (result.status === 'completed') {
|
|
112
178
|
// Authentication completed successfully
|
|
179
|
+
console.log('[Link Auth] ✅ Authentication COMPLETED! Session:', sessionKey);
|
|
180
|
+
console.log('[Link Auth] Full completion result:', JSON.stringify(result, null, 2));
|
|
113
181
|
this.stopPolling();
|
|
114
182
|
// Authentication completed successfully
|
|
115
183
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
@@ -120,6 +188,7 @@ class LinkHandler {
|
|
|
120
188
|
});
|
|
121
189
|
}
|
|
122
190
|
// Return the authentication result
|
|
191
|
+
this.pollingReject = undefined; // Clear reject function on success
|
|
123
192
|
resolve({
|
|
124
193
|
authenticated: true,
|
|
125
194
|
credential: result.credential || sessionKey,
|
|
@@ -134,6 +203,8 @@ class LinkHandler {
|
|
|
134
203
|
}
|
|
135
204
|
else if (result.status === 'pending') {
|
|
136
205
|
// Continue polling
|
|
206
|
+
console.log('[Link Auth] Status still pending, continuing to poll...');
|
|
207
|
+
attempts++; // Increment attempts after successful poll
|
|
137
208
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
138
209
|
options.onStatusUpdate({
|
|
139
210
|
status: 'pending',
|
|
@@ -141,9 +212,15 @@ class LinkHandler {
|
|
|
141
212
|
});
|
|
142
213
|
}
|
|
143
214
|
}
|
|
215
|
+
else {
|
|
216
|
+
// Unexpected status value
|
|
217
|
+
console.log('[Link Auth] ⚠️ Unexpected status value:', result.status, 'Full result:', JSON.stringify(result, null, 2));
|
|
218
|
+
attempts++; // Increment for unexpected status too
|
|
219
|
+
}
|
|
144
220
|
}
|
|
145
221
|
else if (response.status === 410) {
|
|
146
222
|
// Session expired
|
|
223
|
+
console.log('[Link Auth] ❌ Session expired (410)');
|
|
147
224
|
this.stopPolling();
|
|
148
225
|
const errorData = yield response.json().catch(() => ({ message: 'Session expired' }));
|
|
149
226
|
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
@@ -159,8 +236,10 @@ class LinkHandler {
|
|
|
159
236
|
}
|
|
160
237
|
else if (response.status === 422) {
|
|
161
238
|
// Authentication failed
|
|
239
|
+
console.log('[Link Auth] ❌ Authentication failed (422)');
|
|
162
240
|
this.stopPolling();
|
|
163
241
|
const errorData = yield response.json().catch(() => ({ message: 'Authentication failed' }));
|
|
242
|
+
console.log('[Link Auth] Error data:', JSON.stringify(errorData, null, 2));
|
|
164
243
|
const isUserCancelled = errorData.code === 'USER_CANCELLED';
|
|
165
244
|
const errorMsg = isUserCancelled
|
|
166
245
|
? 'User cancelled authentication'
|
|
@@ -176,6 +255,7 @@ class LinkHandler {
|
|
|
176
255
|
}
|
|
177
256
|
else if (response.status === 404) {
|
|
178
257
|
// Session not found
|
|
258
|
+
console.log('[Link Auth] ❌ Session not found (404)');
|
|
179
259
|
this.stopPolling();
|
|
180
260
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
181
261
|
options.onStatusUpdate({
|
|
@@ -187,16 +267,42 @@ class LinkHandler {
|
|
|
187
267
|
}
|
|
188
268
|
else if (response.status === 400) {
|
|
189
269
|
// Invalid session key
|
|
270
|
+
console.log('[Link Auth] ❌ Invalid session key (400)');
|
|
190
271
|
this.stopPolling();
|
|
191
272
|
const errorData = yield response.json().catch(() => ({ message: 'Invalid session key' }));
|
|
273
|
+
console.log('[Link Auth] Error data:', JSON.stringify(errorData, null, 2));
|
|
192
274
|
reject(new Error(errorData.message || 'Invalid session key'));
|
|
193
275
|
}
|
|
194
276
|
else {
|
|
195
277
|
// Unexpected status - continue polling
|
|
278
|
+
console.log('[Link Auth] ⚠️ Unexpected HTTP status:', response.status, 'continuing to poll...');
|
|
279
|
+
attempts++; // Increment for unexpected HTTP status
|
|
280
|
+
try {
|
|
281
|
+
const body = yield response.text();
|
|
282
|
+
console.log('[Link Auth] Response body:', body);
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
console.log('[Link Auth] Could not read response body');
|
|
286
|
+
}
|
|
196
287
|
}
|
|
197
288
|
}
|
|
198
289
|
catch (error) {
|
|
199
290
|
// Network or other error - continue polling
|
|
291
|
+
console.error('[Link Auth] 🔴 Polling error:', error.message || error);
|
|
292
|
+
attempts++; // Increment for error case
|
|
293
|
+
console.error('[Link Auth] Error details:', {
|
|
294
|
+
name: error.name,
|
|
295
|
+
message: error.message,
|
|
296
|
+
stack: error.stack,
|
|
297
|
+
statusUrl: statusUrl,
|
|
298
|
+
attempt: attempts,
|
|
299
|
+
error: error
|
|
300
|
+
});
|
|
301
|
+
// Check if it's a CORS error (common on mobile)
|
|
302
|
+
if (error.message && error.message.toLowerCase().includes('failed')) {
|
|
303
|
+
console.error('[Link Auth] ⚠️ Possible CORS issue. Status URL:', statusUrl);
|
|
304
|
+
console.error('[Link Auth] Make sure the API endpoint allows CORS from your ngrok domain');
|
|
305
|
+
}
|
|
200
306
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
201
307
|
options.onStatusUpdate({
|
|
202
308
|
status: 'pending',
|
|
@@ -204,6 +310,10 @@ class LinkHandler {
|
|
|
204
310
|
});
|
|
205
311
|
}
|
|
206
312
|
}
|
|
313
|
+
finally {
|
|
314
|
+
// Always clear the polling flag when done
|
|
315
|
+
this.isPollingInProgress = false;
|
|
316
|
+
}
|
|
207
317
|
});
|
|
208
318
|
// Start initial poll immediately
|
|
209
319
|
poll();
|
|
@@ -216,7 +326,9 @@ class LinkHandler {
|
|
|
216
326
|
* Stop polling
|
|
217
327
|
*/
|
|
218
328
|
stopPolling() {
|
|
219
|
-
|
|
329
|
+
console.log('[Link Auth] 🏁 Stopping polling');
|
|
330
|
+
this.isPollingActive = false;
|
|
331
|
+
this.isPollingInProgress = false;
|
|
220
332
|
if (this.pollingInterval) {
|
|
221
333
|
clearInterval(this.pollingInterval);
|
|
222
334
|
this.pollingInterval = undefined;
|
|
@@ -251,15 +363,31 @@ class LinkHandler {
|
|
|
251
363
|
this.stopPolling();
|
|
252
364
|
this.isCancelled = false;
|
|
253
365
|
this.onCancel = undefined;
|
|
366
|
+
this.pollingReject = undefined;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check if polling is currently active
|
|
370
|
+
*/
|
|
371
|
+
isPolling() {
|
|
372
|
+
return this.pollingInterval !== undefined;
|
|
254
373
|
}
|
|
255
374
|
/**
|
|
256
375
|
* Cancel the ongoing authentication
|
|
257
376
|
*/
|
|
258
377
|
cancel() {
|
|
259
378
|
var _a;
|
|
379
|
+
console.log('[Link Auth] Cancelling authentication');
|
|
260
380
|
this.isCancelled = true;
|
|
261
381
|
this.stopPolling();
|
|
262
382
|
(_a = this.onCancel) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
383
|
+
// Immediately reject the polling promise
|
|
384
|
+
if (this.pollingReject) {
|
|
385
|
+
this.pollingReject({
|
|
386
|
+
code: 'USER_DENIED',
|
|
387
|
+
message: 'Authentication cancelled by user'
|
|
388
|
+
});
|
|
389
|
+
this.pollingReject = undefined;
|
|
390
|
+
}
|
|
263
391
|
}
|
|
264
392
|
}
|
|
265
393
|
exports.LinkHandler = LinkHandler;
|
|
@@ -72,7 +72,7 @@ export interface AuthConfig extends PhoneAuthCallbacks {
|
|
|
72
72
|
pollingInterval?: number;
|
|
73
73
|
/**
|
|
74
74
|
* Maximum polling attempts before timeout
|
|
75
|
-
* @default
|
|
75
|
+
* @default 30 (1 minute with 2s interval)
|
|
76
76
|
*/
|
|
77
77
|
maxPollingAttempts?: number;
|
|
78
78
|
/**
|
|
@@ -26,6 +26,10 @@ export declare class AuthModal {
|
|
|
26
26
|
private closeCallback?;
|
|
27
27
|
constructor(options?: InvokeOptions['modalOptions'], callbacks?: InvokeOptions['callbacks']);
|
|
28
28
|
private handleEscapeKey;
|
|
29
|
+
/**
|
|
30
|
+
* Escape HTML to prevent XSS attacks
|
|
31
|
+
*/
|
|
32
|
+
private escapeHtml;
|
|
29
33
|
/**
|
|
30
34
|
* Shows the modal with a QR code for desktop authentication
|
|
31
35
|
* Supports both single QR code (legacy) and dual-platform QR codes (iOS + Android)
|
|
@@ -47,6 +47,17 @@ class AuthModal {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Escape HTML to prevent XSS attacks
|
|
52
|
+
*/
|
|
53
|
+
escapeHtml(unsafe) {
|
|
54
|
+
return unsafe
|
|
55
|
+
.replace(/&/g, "&")
|
|
56
|
+
.replace(/</g, "<")
|
|
57
|
+
.replace(/>/g, ">")
|
|
58
|
+
.replace(/"/g, """)
|
|
59
|
+
.replace(/'/g, "'");
|
|
60
|
+
}
|
|
50
61
|
/**
|
|
51
62
|
* Shows the modal with a QR code for desktop authentication
|
|
52
63
|
* Supports both single QR code (legacy) and dual-platform QR codes (iOS + Android)
|
|
@@ -75,7 +86,7 @@ class AuthModal {
|
|
|
75
86
|
// Only iOS QR code available - show single QR
|
|
76
87
|
this.createModal(`
|
|
77
88
|
<div class="glide-auth-qr-container">
|
|
78
|
-
<img src="${qrCodeData.iosQRCode}" alt="QR Code" class="glide-auth-qr-code" />
|
|
89
|
+
<img src="${this.escapeHtml(qrCodeData.iosQRCode)}" alt="QR Code" class="glide-auth-qr-code" />
|
|
79
90
|
<p class="glide-auth-status">Scan with your iPhone to authenticate</p>
|
|
80
91
|
</div>
|
|
81
92
|
`);
|
|
@@ -86,8 +97,8 @@ class AuthModal {
|
|
|
86
97
|
// Legacy single QR code
|
|
87
98
|
this.createModal(`
|
|
88
99
|
<div class="glide-auth-qr-container">
|
|
89
|
-
<img src="${qrCodeData}" alt="QR Code" class="glide-auth-qr-code" />
|
|
90
|
-
<p class="glide-auth-status">${statusMessage}</p>
|
|
100
|
+
<img src="${this.escapeHtml(qrCodeData)}" alt="QR Code" class="glide-auth-qr-code" />
|
|
101
|
+
<p class="glide-auth-status">${this.escapeHtml(statusMessage)}</p>
|
|
91
102
|
</div>
|
|
92
103
|
`);
|
|
93
104
|
}
|
|
@@ -118,11 +129,11 @@ class AuthModal {
|
|
|
118
129
|
<!-- QR Code Image -->
|
|
119
130
|
<img
|
|
120
131
|
id="glide-qr-code-img"
|
|
121
|
-
src="${qrCodeData.iosQRCode}"
|
|
132
|
+
src="${this.escapeHtml(qrCodeData.iosQRCode)}"
|
|
122
133
|
alt="QR Code"
|
|
123
134
|
class="glide-auth-qr-code"
|
|
124
|
-
data-ios="${qrCodeData.iosQRCode}"
|
|
125
|
-
data-android="${qrCodeData.androidQRCode}"
|
|
135
|
+
data-ios="${this.escapeHtml(qrCodeData.iosQRCode)}"
|
|
136
|
+
data-android="${this.escapeHtml(qrCodeData.androidQRCode || '')}"
|
|
126
137
|
/>
|
|
127
138
|
|
|
128
139
|
<!-- Status Message -->
|
package/dist/core/version.js
CHANGED