@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.
@@ -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.isPolling = false;
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) || 150; // 5 minutes with 2s interval
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.isPolling = true;
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.isPolling) {
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
- attempts++;
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: 'Authentication timeout'
109
+ message: `Authentication timeout after ${timeoutMessage}`
84
110
  });
85
111
  }
86
- reject(new Error('Authentication timeout after 5 minutes'));
112
+ reject(new Error(`Authentication timeout after ${timeoutMessage}`));
87
113
  return;
88
114
  }
89
115
  // Build public status endpoint URL
90
- let statusUrl;
91
- // First priority: use status_url from link data if provided
92
- if (linkData.status_url) {
93
- statusUrl = linkData.status_url;
94
- console.log('[Link Auth] Using status URL from link data:', statusUrl);
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
- this.isPolling = false;
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 150 (5 minutes with 2s interval)
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, "&lt;")
57
+ .replace(/>/g, "&gt;")
58
+ .replace(/"/g, "&quot;")
59
+ .replace(/'/g, "&#039;");
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 -->
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SDK_VERSION = void 0;
4
4
  // SDK version - injected at build time
5
- exports.SDK_VERSION = '4.4.8-beta.2';
5
+ exports.SDK_VERSION = '4.4.8';