@glideidentity/web-client-sdk 5.0.1-beta.0 → 5.1.1-beta.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/README.md +8 -108
- package/dist/adapters/angular/index.js +1 -0
- package/dist/adapters/angular/phone-auth.service.d.ts +18 -0
- package/dist/adapters/angular/phone-auth.service.js +26 -0
- package/dist/adapters/react/index.js +3 -0
- package/dist/adapters/react/useClient.js +1 -0
- package/dist/adapters/react/usePhoneAuth.js +16 -1
- package/dist/adapters/vanilla/client.js +1 -0
- package/dist/adapters/vanilla/index.js +1 -0
- package/dist/adapters/vanilla/phone-auth.js +31 -0
- package/dist/adapters/vue/index.js +4 -0
- package/dist/adapters/vue/useClient.js +5 -0
- package/dist/adapters/vue/usePhoneAuth.js +20 -1
- package/dist/browser/web-client-sdk.min.js +1 -2
- package/dist/browser.js +6 -0
- package/dist/core/client.js +12 -0
- package/dist/core/logger.js +81 -1
- package/dist/core/phone-auth/api-types.d.ts +1 -4
- package/dist/core/phone-auth/api-types.js +83 -0
- package/dist/core/phone-auth/client.js +374 -38
- package/dist/core/phone-auth/error-utils.js +83 -1
- package/dist/core/phone-auth/index.d.ts +1 -1
- package/dist/core/phone-auth/index.js +2 -2
- package/dist/core/phone-auth/status-types.d.ts +78 -0
- package/dist/core/phone-auth/status-types.js +17 -0
- package/dist/core/phone-auth/strategies/desktop.d.ts +2 -0
- package/dist/core/phone-auth/strategies/desktop.js +136 -13
- package/dist/core/phone-auth/strategies/index.d.ts +4 -0
- package/dist/core/phone-auth/strategies/index.js +4 -0
- package/dist/core/phone-auth/strategies/link.d.ts +2 -0
- package/dist/core/phone-auth/strategies/link.js +97 -13
- package/dist/core/phone-auth/strategies/ts43.d.ts +19 -0
- package/dist/core/phone-auth/strategies/ts43.js +33 -2
- package/dist/core/phone-auth/strategies/types.js +4 -0
- package/dist/core/phone-auth/type-guards.js +131 -0
- package/dist/core/phone-auth/types.d.ts +5 -0
- package/dist/core/phone-auth/types.js +32 -0
- package/dist/core/phone-auth/ui/mobile-debug-console.js +28 -2
- package/dist/core/phone-auth/ui/modal.d.ts +55 -33
- package/dist/core/phone-auth/ui/modal.js +422 -889
- package/dist/core/phone-auth/validation-utils.d.ts +0 -9
- package/dist/core/phone-auth/validation-utils.js +34 -25
- package/dist/core/version.js +2 -1
- package/dist/esm/adapters/angular/index.js +1 -0
- package/dist/esm/adapters/angular/phone-auth.service.d.ts +18 -0
- package/dist/esm/adapters/angular/phone-auth.service.js +26 -0
- package/dist/esm/adapters/react/index.js +3 -0
- package/dist/esm/adapters/react/useClient.js +1 -0
- package/dist/esm/adapters/react/usePhoneAuth.js +16 -1
- package/dist/esm/adapters/vanilla/client.js +1 -0
- package/dist/esm/adapters/vanilla/index.js +1 -0
- package/dist/esm/adapters/vanilla/phone-auth.d.ts +24 -0
- package/dist/esm/adapters/vanilla/phone-auth.js +31 -0
- package/dist/esm/adapters/vue/index.js +4 -0
- package/dist/esm/adapters/vue/useClient.js +5 -0
- package/dist/esm/adapters/vue/usePhoneAuth.js +20 -1
- package/dist/esm/browser.js +6 -0
- package/dist/esm/core/client.d.ts +10 -0
- package/dist/esm/core/client.js +12 -0
- package/dist/esm/core/logger.d.ts +53 -0
- package/dist/esm/core/logger.js +81 -1
- package/dist/esm/core/phone-auth/api-types.d.ts +313 -1
- package/dist/esm/core/phone-auth/api-types.js +83 -0
- package/dist/esm/core/phone-auth/client.d.ts +144 -0
- package/dist/esm/core/phone-auth/client.js +375 -39
- package/dist/esm/core/phone-auth/error-utils.d.ts +29 -0
- package/dist/esm/core/phone-auth/error-utils.js +83 -1
- package/dist/esm/core/phone-auth/index.d.ts +1 -1
- package/dist/esm/core/phone-auth/index.js +4 -2
- package/dist/esm/core/phone-auth/status-types.d.ts +78 -0
- package/dist/esm/core/phone-auth/status-types.js +17 -0
- package/dist/esm/core/phone-auth/strategies/desktop.d.ts +65 -0
- package/dist/esm/core/phone-auth/strategies/desktop.js +136 -13
- package/dist/esm/core/phone-auth/strategies/index.d.ts +4 -0
- package/dist/esm/core/phone-auth/strategies/index.js +4 -0
- package/dist/esm/core/phone-auth/strategies/link.d.ts +50 -0
- package/dist/esm/core/phone-auth/strategies/link.js +97 -13
- package/dist/esm/core/phone-auth/strategies/ts43.d.ts +19 -0
- package/dist/esm/core/phone-auth/strategies/ts43.js +33 -2
- package/dist/esm/core/phone-auth/strategies/types.d.ts +13 -0
- package/dist/esm/core/phone-auth/strategies/types.js +4 -0
- package/dist/esm/core/phone-auth/type-guards.d.ts +128 -0
- package/dist/esm/core/phone-auth/type-guards.js +131 -0
- package/dist/esm/core/phone-auth/types.d.ts +113 -0
- package/dist/esm/core/phone-auth/types.js +32 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +4 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +28 -2
- package/dist/esm/core/phone-auth/ui/modal.d.ts +68 -27
- package/dist/esm/core/phone-auth/ui/modal.js +422 -889
- package/dist/esm/core/phone-auth/validation-utils.d.ts +26 -4
- package/dist/esm/core/phone-auth/validation-utils.js +34 -24
- package/dist/esm/core/types.d.ts +35 -0
- package/dist/esm/core/version.js +2 -1
- package/dist/esm/index.js +9 -1
- package/dist/index.js +7 -0
- package/package.json +1 -1
- package/dist/browser/web-client-sdk.min.js.LICENSE.txt +0 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop Strategy Handler
|
|
3
|
+
* Handles QR code-based authentication for desktop browsers
|
|
4
|
+
* Manages QR code display and polling for authentication status
|
|
5
|
+
*/
|
|
1
6
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
7
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
8
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -13,33 +18,49 @@ export class DesktopHandler {
|
|
|
13
18
|
this.isCancelled = false;
|
|
14
19
|
this.isPollingInProgress = false;
|
|
15
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Maps backend HTTP status codes to client status
|
|
23
|
+
* @param httpStatus HTTP status code from backend
|
|
24
|
+
* @param bodyStatus Optional status from response body (for 200 OK responses)
|
|
25
|
+
* @returns Mapped status string for client use
|
|
26
|
+
*/
|
|
16
27
|
mapBackendStatus(httpStatus, bodyStatus) {
|
|
17
28
|
switch (httpStatus) {
|
|
18
29
|
case 200:
|
|
30
|
+
// For 200 OK, check the body status
|
|
19
31
|
return bodyStatus === 'completed' ? 'authenticated' : 'pending';
|
|
20
32
|
case 410:
|
|
21
33
|
return 'expired';
|
|
22
34
|
case 422:
|
|
23
|
-
return 'error';
|
|
35
|
+
return 'error'; // Failed or cancelled
|
|
24
36
|
case 404:
|
|
25
|
-
return 'error';
|
|
37
|
+
return 'error'; // Session not found
|
|
26
38
|
case 400:
|
|
27
|
-
return 'error';
|
|
39
|
+
return 'error'; // Invalid session key
|
|
28
40
|
default:
|
|
29
41
|
return 'error';
|
|
30
42
|
}
|
|
31
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Invoke desktop authentication with QR code
|
|
46
|
+
* Returns QR code data for display and starts polling if endpoint is provided
|
|
47
|
+
*/
|
|
32
48
|
invoke(data, options) {
|
|
33
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
50
|
const desktopData = data.data;
|
|
51
|
+
// Extract QR code from nested or flat structure, checking multiple field names
|
|
35
52
|
let qrCode;
|
|
36
53
|
let sessionId;
|
|
37
54
|
let pollingEndpoint;
|
|
38
55
|
let pollingInterval;
|
|
39
56
|
let expiresIn;
|
|
57
|
+
// Check nested structure first (data.data.qr_code_image or data.data.qr_code)
|
|
40
58
|
if (desktopData && desktopData.data && typeof desktopData.data === 'object') {
|
|
41
59
|
const innerData = desktopData.data;
|
|
60
|
+
// Try to extract from inner data
|
|
61
|
+
// Support both single QR (qr_code_image) and dual-platform QR (ios/android)
|
|
42
62
|
if (innerData.ios_qr_image && innerData.android_qr_image) {
|
|
63
|
+
// Dual-platform QR format - both must be present
|
|
43
64
|
qrCode = {
|
|
44
65
|
iosQRCode: innerData.ios_qr_image,
|
|
45
66
|
androidQRCode: innerData.android_qr_image,
|
|
@@ -48,9 +69,11 @@ export class DesktopHandler {
|
|
|
48
69
|
};
|
|
49
70
|
}
|
|
50
71
|
else if (innerData.ios_qr_image || innerData.android_qr_image) {
|
|
72
|
+
// Only one platform QR - use as single QR format
|
|
51
73
|
qrCode = innerData.ios_qr_image || innerData.android_qr_image;
|
|
52
74
|
}
|
|
53
75
|
else {
|
|
76
|
+
// Single QR format (legacy)
|
|
54
77
|
qrCode = innerData.qr_code_image || innerData.qr_code;
|
|
55
78
|
}
|
|
56
79
|
sessionId = innerData.session_id;
|
|
@@ -58,6 +81,7 @@ export class DesktopHandler {
|
|
|
58
81
|
pollingInterval = innerData.polling_interval;
|
|
59
82
|
expiresIn = innerData.expires_in;
|
|
60
83
|
}
|
|
84
|
+
// Fall back to flat structure if no QR code found
|
|
61
85
|
if (!qrCode && desktopData) {
|
|
62
86
|
qrCode = desktopData.qr_code_image || desktopData.qr_code;
|
|
63
87
|
sessionId = sessionId || desktopData.session_id;
|
|
@@ -65,47 +89,65 @@ export class DesktopHandler {
|
|
|
65
89
|
pollingInterval = pollingInterval || desktopData.polling_interval;
|
|
66
90
|
expiresIn = expiresIn || desktopData.expires_in;
|
|
67
91
|
}
|
|
92
|
+
// Validate QR code exists
|
|
68
93
|
if (!qrCode) {
|
|
69
94
|
throw new Error('Invalid desktop authentication data: missing QR code');
|
|
70
95
|
}
|
|
96
|
+
// Validate session ID exists
|
|
71
97
|
if (!sessionId) {
|
|
72
98
|
throw new Error('Invalid desktop authentication data: missing session ID');
|
|
73
99
|
}
|
|
100
|
+
// Notify that QR code is ready
|
|
74
101
|
if (options === null || options === void 0 ? void 0 : options.onQRCodeReady) {
|
|
75
102
|
options.onQRCodeReady(qrCode);
|
|
76
103
|
}
|
|
104
|
+
// Use polling endpoint with this priority:
|
|
105
|
+
// 1. Developer-provided endpoint from options (highest priority)
|
|
106
|
+
// 2. Backend-provided endpoint
|
|
107
|
+
// 3. Hardcoded fallback
|
|
77
108
|
console.log('[Desktop QR] Polling endpoint selection:');
|
|
78
109
|
console.log(' - options?.pollingEndpoint:', options === null || options === void 0 ? void 0 : options.pollingEndpoint);
|
|
79
110
|
console.log(' - backend pollingEndpoint:', pollingEndpoint);
|
|
80
111
|
let finalPollingEndpoint = options === null || options === void 0 ? void 0 : options.pollingEndpoint;
|
|
81
112
|
let endpointSource = 'options';
|
|
82
113
|
if (!finalPollingEndpoint) {
|
|
83
|
-
finalPollingEndpoint = pollingEndpoint;
|
|
114
|
+
finalPollingEndpoint = pollingEndpoint; // Backend-provided status_url
|
|
84
115
|
endpointSource = 'backend';
|
|
85
116
|
}
|
|
86
117
|
if (!finalPollingEndpoint) {
|
|
118
|
+
// Use hardcoded fallback - this will be constructed in the polling function
|
|
87
119
|
console.log('[Desktop QR] No polling endpoint provided, will use hardcoded fallback');
|
|
88
120
|
endpointSource = 'fallback';
|
|
89
121
|
}
|
|
90
122
|
console.log('[Desktop QR] Selected endpoint:', finalPollingEndpoint, 'from source:', endpointSource);
|
|
123
|
+
// Start polling for authentication status
|
|
91
124
|
const finalPollingInterval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || pollingInterval || 2000;
|
|
92
|
-
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30;
|
|
125
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30; // Default to 1 minute
|
|
93
126
|
console.log(`[Desktop QR] Starting polling - endpoint source: ${endpointSource}, interval: ${finalPollingInterval}ms, max attempts: ${maxAttempts}`);
|
|
94
|
-
return this.startPolling(finalPollingEndpoint || '',
|
|
127
|
+
return this.startPolling(finalPollingEndpoint || '', // Pass empty string if undefined, will use fallback
|
|
128
|
+
sessionId, finalPollingInterval, maxAttempts, expiresIn || 300, // Default 5 minutes expiry
|
|
129
|
+
options, endpointSource // Pass the endpoint source for logging
|
|
130
|
+
);
|
|
95
131
|
});
|
|
96
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Start polling for authentication status
|
|
135
|
+
*/
|
|
97
136
|
startPolling(endpoint_1, sessionId_1, interval_1, maxAttempts_1, expiresIn_1, options_1) {
|
|
98
137
|
return __awaiter(this, arguments, void 0, function* (endpoint, sessionId, interval, maxAttempts, expiresIn, options, endpointSource = 'unknown') {
|
|
99
138
|
const startTime = Date.now();
|
|
100
139
|
const expiryTime = startTime + (expiresIn * 1000);
|
|
101
140
|
return new Promise((resolve, reject) => {
|
|
141
|
+
// Store the reject function so we can call it from cancel()
|
|
102
142
|
this.pollingReject = reject;
|
|
103
143
|
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
// Skip if another poll is already in progress
|
|
104
145
|
if (this.isPollingInProgress) {
|
|
105
146
|
return;
|
|
106
147
|
}
|
|
107
148
|
try {
|
|
108
149
|
this.isPollingInProgress = true;
|
|
150
|
+
// Check if cancelled
|
|
109
151
|
if (this.isCancelled) {
|
|
110
152
|
this.stopPolling();
|
|
111
153
|
if (options === null || options === void 0 ? void 0 : options.onCancel) {
|
|
@@ -117,12 +159,14 @@ export class DesktopHandler {
|
|
|
117
159
|
message: 'Authentication cancelled'
|
|
118
160
|
});
|
|
119
161
|
}
|
|
162
|
+
// Reject the promise with a cancellation error
|
|
120
163
|
reject({
|
|
121
164
|
code: 'USER_DENIED',
|
|
122
165
|
message: 'Authentication cancelled by user'
|
|
123
166
|
});
|
|
124
167
|
return;
|
|
125
168
|
}
|
|
169
|
+
// Check if expired
|
|
126
170
|
if (Date.now() > expiryTime) {
|
|
127
171
|
this.stopPolling();
|
|
128
172
|
if (options === null || options === void 0 ? void 0 : options.onExpired) {
|
|
@@ -140,6 +184,7 @@ export class DesktopHandler {
|
|
|
140
184
|
});
|
|
141
185
|
return;
|
|
142
186
|
}
|
|
187
|
+
// Check max attempts
|
|
143
188
|
if (this.pollingAttempts >= maxAttempts) {
|
|
144
189
|
this.stopPolling();
|
|
145
190
|
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
@@ -157,35 +202,58 @@ export class DesktopHandler {
|
|
|
157
202
|
});
|
|
158
203
|
return;
|
|
159
204
|
}
|
|
205
|
+
// Build public status endpoint URL - use relative path for proxied requests
|
|
160
206
|
let statusUrl;
|
|
207
|
+
// Extract base URL and construct public endpoint
|
|
161
208
|
if (endpoint && (endpoint.startsWith('http://') || endpoint.startsWith('https://'))) {
|
|
162
|
-
|
|
163
|
-
statusUrl =
|
|
209
|
+
// Full URL provided - use as is
|
|
210
|
+
statusUrl = endpoint;
|
|
164
211
|
}
|
|
165
212
|
else if (endpoint && endpoint !== '') {
|
|
213
|
+
// Relative path provided (e.g. '/api/phone-auth/status')
|
|
214
|
+
// Append session ID to the provided endpoint
|
|
166
215
|
statusUrl = `${endpoint}/${sessionId}`;
|
|
167
216
|
}
|
|
168
217
|
else {
|
|
218
|
+
// No endpoint provided - use hardcoded fallback
|
|
219
|
+
// This ensures it goes through the same domain and uses the app's API configuration
|
|
169
220
|
statusUrl = `/api/phone-auth/status/${sessionId}`;
|
|
170
221
|
}
|
|
222
|
+
// Poll the public endpoint - no authentication required
|
|
171
223
|
console.log(`[Desktop QR] Polling status (attempt ${this.pollingAttempts}/${maxAttempts})`);
|
|
172
224
|
console.log(`[Desktop QR] Using ${endpointSource} endpoint: ${statusUrl}`);
|
|
225
|
+
// Build headers
|
|
226
|
+
const headers = {
|
|
227
|
+
'Accept': 'application/json'
|
|
228
|
+
};
|
|
229
|
+
// Add developer header if devEnv is set
|
|
230
|
+
if (options === null || options === void 0 ? void 0 : options.devEnv) {
|
|
231
|
+
headers['developer'] = options.devEnv;
|
|
232
|
+
console.log(`[Desktop QR] Adding developer header: ${options.devEnv}`);
|
|
233
|
+
}
|
|
173
234
|
const response = yield fetch(statusUrl, {
|
|
174
235
|
method: 'GET',
|
|
175
|
-
headers
|
|
176
|
-
'Accept': 'application/json'
|
|
177
|
-
}
|
|
236
|
+
headers
|
|
178
237
|
});
|
|
179
238
|
console.log(`[Desktop QR] Status response: ${response.status} ${response.statusText}`);
|
|
239
|
+
// Backend Status Code Mapping:
|
|
240
|
+
// - 200 OK: Session exists (body has 'pending' or 'completed' status)
|
|
241
|
+
// - 410 Gone: Session expired after timeout
|
|
242
|
+
// - 422 Unprocessable Entity: Authentication failed or cancelled
|
|
243
|
+
// - 404 Not Found: Session doesn't exist
|
|
244
|
+
// - 400 Bad Request: Invalid session key format
|
|
245
|
+
// Handle response based on HTTP status code
|
|
180
246
|
if (response.status === 200) {
|
|
181
247
|
const result = yield response.json();
|
|
182
248
|
if (result.status === 'completed') {
|
|
249
|
+
// Authentication completed successfully
|
|
183
250
|
this.stopPolling();
|
|
251
|
+
// Close the modal if it exists
|
|
184
252
|
const modal = document.querySelector('[style*="position: fixed"]');
|
|
185
253
|
if (modal && modal.querySelector('#desktop-auth-status')) {
|
|
186
254
|
setTimeout(() => {
|
|
187
255
|
modal.remove();
|
|
188
|
-
}, 500);
|
|
256
|
+
}, 500); // Brief delay to show success message
|
|
189
257
|
}
|
|
190
258
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
191
259
|
options.onStatusUpdate({
|
|
@@ -194,7 +262,8 @@ export class DesktopHandler {
|
|
|
194
262
|
data: result
|
|
195
263
|
});
|
|
196
264
|
}
|
|
197
|
-
|
|
265
|
+
// Return the session data for next steps
|
|
266
|
+
this.pollingReject = undefined; // Clear the reject function on success
|
|
198
267
|
resolve({
|
|
199
268
|
authenticated: true,
|
|
200
269
|
credential: result.credential || result.session_key || sessionId,
|
|
@@ -208,6 +277,7 @@ export class DesktopHandler {
|
|
|
208
277
|
});
|
|
209
278
|
}
|
|
210
279
|
else if (result.status === 'pending') {
|
|
280
|
+
// Continue polling
|
|
211
281
|
this.pollingAttempts++;
|
|
212
282
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
213
283
|
options.onStatusUpdate({
|
|
@@ -218,8 +288,10 @@ export class DesktopHandler {
|
|
|
218
288
|
}
|
|
219
289
|
}
|
|
220
290
|
else if (response.status === 410) {
|
|
291
|
+
// Session expired
|
|
221
292
|
this.stopPolling();
|
|
222
293
|
const errorData = yield response.json().catch(() => ({ message: 'Session expired' }));
|
|
294
|
+
// Close the modal on error
|
|
223
295
|
const modal = document.querySelector('[style*="position: fixed"]');
|
|
224
296
|
if (modal && modal.querySelector('#desktop-auth-status')) {
|
|
225
297
|
setTimeout(() => {
|
|
@@ -241,8 +313,10 @@ export class DesktopHandler {
|
|
|
241
313
|
});
|
|
242
314
|
}
|
|
243
315
|
else if (response.status === 422) {
|
|
316
|
+
// Authentication failed
|
|
244
317
|
this.stopPolling();
|
|
245
318
|
const errorData = yield response.json().catch(() => ({ message: 'Authentication failed' }));
|
|
319
|
+
// Close the modal on error
|
|
246
320
|
const modal = document.querySelector('[style*="position: fixed"]');
|
|
247
321
|
if (modal && modal.querySelector('#desktop-auth-status')) {
|
|
248
322
|
setTimeout(() => {
|
|
@@ -264,6 +338,7 @@ export class DesktopHandler {
|
|
|
264
338
|
});
|
|
265
339
|
}
|
|
266
340
|
else if (response.status === 404) {
|
|
341
|
+
// Session not found
|
|
267
342
|
this.stopPolling();
|
|
268
343
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
269
344
|
options.onStatusUpdate({
|
|
@@ -277,6 +352,7 @@ export class DesktopHandler {
|
|
|
277
352
|
});
|
|
278
353
|
}
|
|
279
354
|
else if (response.status === 400) {
|
|
355
|
+
// Invalid session key
|
|
280
356
|
this.stopPolling();
|
|
281
357
|
const errorData = yield response.json().catch(() => ({ message: 'Invalid session key' }));
|
|
282
358
|
resolve({
|
|
@@ -285,10 +361,12 @@ export class DesktopHandler {
|
|
|
285
361
|
});
|
|
286
362
|
}
|
|
287
363
|
else {
|
|
364
|
+
// Unexpected status - continue polling
|
|
288
365
|
this.pollingAttempts++;
|
|
289
366
|
}
|
|
290
367
|
}
|
|
291
368
|
catch (error) {
|
|
369
|
+
// Network or other error - continue polling
|
|
292
370
|
this.pollingAttempts++;
|
|
293
371
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
294
372
|
options.onStatusUpdate({
|
|
@@ -298,14 +376,20 @@ export class DesktopHandler {
|
|
|
298
376
|
}
|
|
299
377
|
}
|
|
300
378
|
finally {
|
|
379
|
+
// Always clear the polling flag when done
|
|
301
380
|
this.isPollingInProgress = false;
|
|
302
381
|
}
|
|
303
382
|
});
|
|
383
|
+
// Start initial poll
|
|
304
384
|
poll();
|
|
385
|
+
// Set up interval for subsequent polls
|
|
305
386
|
this.pollingIntervalId = setInterval(poll, interval);
|
|
306
387
|
});
|
|
307
388
|
});
|
|
308
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Stop polling
|
|
392
|
+
*/
|
|
309
393
|
stopPolling() {
|
|
310
394
|
if (this.pollingIntervalId) {
|
|
311
395
|
console.log('[Desktop QR] Stopping polling');
|
|
@@ -314,10 +398,18 @@ export class DesktopHandler {
|
|
|
314
398
|
}
|
|
315
399
|
this.pollingAttempts = 0;
|
|
316
400
|
this.isPollingInProgress = false;
|
|
401
|
+
// Don't clear pollingReject here - it's needed by cancel()
|
|
317
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Check if polling is currently active
|
|
405
|
+
*/
|
|
318
406
|
isPolling() {
|
|
319
407
|
return this.pollingIntervalId !== undefined;
|
|
320
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Format response for backend processing
|
|
411
|
+
* Desktop strategy typically returns the credential from mobile authentication
|
|
412
|
+
*/
|
|
321
413
|
formatResponse(response) {
|
|
322
414
|
if (!response.authenticated || !response.credential) {
|
|
323
415
|
throw new Error('Authentication not completed');
|
|
@@ -327,23 +419,35 @@ export class DesktopHandler {
|
|
|
327
419
|
session: response.session
|
|
328
420
|
};
|
|
329
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Check if desktop authentication is supported
|
|
424
|
+
* Desktop auth with QR codes works in any modern browser
|
|
425
|
+
*/
|
|
330
426
|
isSupported() {
|
|
427
|
+
// Check for required browser APIs
|
|
331
428
|
return typeof window !== 'undefined' &&
|
|
332
429
|
typeof fetch !== 'undefined' &&
|
|
333
430
|
typeof Promise !== 'undefined';
|
|
334
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Clean up resources (stop polling if active)
|
|
434
|
+
*/
|
|
335
435
|
cleanup() {
|
|
336
436
|
this.stopPolling();
|
|
337
437
|
this.isCancelled = false;
|
|
338
438
|
this.onCancel = undefined;
|
|
339
439
|
this.pollingReject = undefined;
|
|
340
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Cancel the ongoing authentication
|
|
443
|
+
*/
|
|
341
444
|
cancel() {
|
|
342
445
|
var _a;
|
|
343
446
|
console.log('[Desktop QR] Cancelling authentication');
|
|
344
447
|
this.isCancelled = true;
|
|
345
448
|
this.stopPolling();
|
|
346
449
|
(_a = this.onCancel) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
450
|
+
// Immediately reject the polling promise
|
|
347
451
|
if (this.pollingReject) {
|
|
348
452
|
this.pollingReject({
|
|
349
453
|
code: 'USER_DENIED',
|
|
@@ -353,14 +457,20 @@ export class DesktopHandler {
|
|
|
353
457
|
}
|
|
354
458
|
}
|
|
355
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Helper function to display QR code in a modal or inline
|
|
462
|
+
*/
|
|
356
463
|
export function createQRCodeDisplay(qrCodeData, options) {
|
|
357
464
|
const container = (options === null || options === void 0 ? void 0 : options.container) || document.createElement('div');
|
|
358
465
|
container.className = 'phone-auth-qr-container';
|
|
466
|
+
// Determine QR code to display
|
|
359
467
|
let qrCodeSrc;
|
|
360
468
|
if (typeof qrCodeData === 'string') {
|
|
469
|
+
// Simple string QR code
|
|
361
470
|
qrCodeSrc = qrCodeData;
|
|
362
471
|
}
|
|
363
472
|
else if (qrCodeData && typeof qrCodeData === 'object') {
|
|
473
|
+
// QRCodeData object - prefer iOS QR if available
|
|
364
474
|
if (qrCodeData.iosQRCode) {
|
|
365
475
|
qrCodeSrc = qrCodeData.iosQRCode;
|
|
366
476
|
}
|
|
@@ -374,6 +484,7 @@ export function createQRCodeDisplay(qrCodeData, options) {
|
|
|
374
484
|
else {
|
|
375
485
|
throw new Error('Invalid qrCodeData: must be string or QRCodeData object');
|
|
376
486
|
}
|
|
487
|
+
// Create QR code image
|
|
377
488
|
const img = document.createElement('img');
|
|
378
489
|
img.src = qrCodeSrc;
|
|
379
490
|
img.alt = 'QR Code for authentication';
|
|
@@ -381,6 +492,7 @@ export function createQRCodeDisplay(qrCodeData, options) {
|
|
|
381
492
|
img.style.height = `${(options === null || options === void 0 ? void 0 : options.size) || 256}px`;
|
|
382
493
|
img.style.display = 'block';
|
|
383
494
|
img.style.margin = '0 auto';
|
|
495
|
+
// Add title if provided
|
|
384
496
|
if (options === null || options === void 0 ? void 0 : options.title) {
|
|
385
497
|
const title = document.createElement('h3');
|
|
386
498
|
title.textContent = options.title;
|
|
@@ -389,6 +501,7 @@ export function createQRCodeDisplay(qrCodeData, options) {
|
|
|
389
501
|
container.appendChild(title);
|
|
390
502
|
}
|
|
391
503
|
container.appendChild(img);
|
|
504
|
+
// Add description if provided
|
|
392
505
|
if (options === null || options === void 0 ? void 0 : options.description) {
|
|
393
506
|
const desc = document.createElement('p');
|
|
394
507
|
desc.textContent = options.description;
|
|
@@ -399,7 +512,12 @@ export function createQRCodeDisplay(qrCodeData, options) {
|
|
|
399
512
|
}
|
|
400
513
|
return container;
|
|
401
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Helper function to create a modal for QR code display
|
|
517
|
+
* Supports both string QR codes and QRCodeData objects for dual-platform support
|
|
518
|
+
*/
|
|
402
519
|
export function showQRCodeModal(qrCodeData, options) {
|
|
520
|
+
// Create modal overlay
|
|
403
521
|
const overlay = document.createElement('div');
|
|
404
522
|
overlay.style.cssText = `
|
|
405
523
|
position: fixed;
|
|
@@ -413,6 +531,7 @@ export function showQRCodeModal(qrCodeData, options) {
|
|
|
413
531
|
justify-content: center;
|
|
414
532
|
z-index: 9999;
|
|
415
533
|
`;
|
|
534
|
+
// Create modal content
|
|
416
535
|
const modal = document.createElement('div');
|
|
417
536
|
modal.style.cssText = `
|
|
418
537
|
background: white;
|
|
@@ -421,6 +540,7 @@ export function showQRCodeModal(qrCodeData, options) {
|
|
|
421
540
|
max-width: 400px;
|
|
422
541
|
position: relative;
|
|
423
542
|
`;
|
|
543
|
+
// Add close button
|
|
424
544
|
const closeBtn = document.createElement('button');
|
|
425
545
|
closeBtn.textContent = '×';
|
|
426
546
|
closeBtn.style.cssText = `
|
|
@@ -440,11 +560,13 @@ export function showQRCodeModal(qrCodeData, options) {
|
|
|
440
560
|
}
|
|
441
561
|
};
|
|
442
562
|
modal.appendChild(closeBtn);
|
|
563
|
+
// Add QR code display
|
|
443
564
|
const qrDisplay = createQRCodeDisplay(qrCodeData, {
|
|
444
565
|
title: (options === null || options === void 0 ? void 0 : options.title) || 'Scan QR Code to Authenticate',
|
|
445
566
|
description: (options === null || options === void 0 ? void 0 : options.description) || 'Use your mobile device to scan this QR code'
|
|
446
567
|
});
|
|
447
568
|
modal.appendChild(qrDisplay);
|
|
569
|
+
// Add loading spinner placeholder
|
|
448
570
|
const statusDiv = document.createElement('div');
|
|
449
571
|
statusDiv.id = 'desktop-auth-status';
|
|
450
572
|
statusDiv.style.cssText = `
|
|
@@ -456,6 +578,7 @@ export function showQRCodeModal(qrCodeData, options) {
|
|
|
456
578
|
modal.appendChild(statusDiv);
|
|
457
579
|
overlay.appendChild(modal);
|
|
458
580
|
document.body.appendChild(overlay);
|
|
581
|
+
// Close modal on overlay click
|
|
459
582
|
overlay.onclick = (e) => {
|
|
460
583
|
if (e.target === overlay) {
|
|
461
584
|
document.body.removeChild(overlay);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Handlers Export
|
|
3
|
+
* Provides implementations for TS43, Link, and Desktop authentication strategies
|
|
4
|
+
*/
|
|
1
5
|
export { TS43Handler } from './ts43';
|
|
2
6
|
export { LinkHandler } from './link';
|
|
3
7
|
export { DesktopHandler, createQRCodeDisplay, showQRCodeModal } from './desktop';
|
|
@@ -1,23 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link Strategy Handler
|
|
3
|
+
* Handles authentication via app links (iOS and Android)
|
|
4
|
+
* Opens authentication app while keeping user on current page
|
|
5
|
+
*/
|
|
1
6
|
import type { StrategyHandler } from './types';
|
|
2
7
|
import type { PrepareResponse } from '../types';
|
|
3
8
|
export interface LinkAuthOptions {
|
|
9
|
+
/** Fixed polling interval in milliseconds (default: 2000) */
|
|
4
10
|
pollingInterval?: number;
|
|
11
|
+
/** Maximum polling attempts before timeout (default: 30 = 1 minute with 2s interval) */
|
|
5
12
|
maxPollingAttempts?: number;
|
|
13
|
+
/** Custom polling endpoint (overrides backend-provided or configured endpoint) */
|
|
6
14
|
pollingEndpoint?: string;
|
|
15
|
+
/** Developer environment (adds 'developer' header to requests) */
|
|
16
|
+
devEnv?: string;
|
|
17
|
+
/** Callback when link is opened */
|
|
7
18
|
onLinkOpened?: () => void;
|
|
19
|
+
/** Callback for polling status updates */
|
|
8
20
|
onStatusUpdate?: (status: PollingStatus) => void;
|
|
21
|
+
/** Callback when authentication times out */
|
|
9
22
|
onTimeout?: () => void;
|
|
23
|
+
/** Callback when authentication is cancelled by user */
|
|
10
24
|
onCancel?: () => void;
|
|
11
25
|
}
|
|
12
26
|
export interface PollingStatus {
|
|
27
|
+
/** Current status of the authentication */
|
|
13
28
|
status: 'pending' | 'authenticated' | 'expired' | 'cancelled' | 'error';
|
|
29
|
+
/** Optional message */
|
|
14
30
|
message?: string;
|
|
31
|
+
/** Authentication result data if status is 'authenticated' */
|
|
15
32
|
data?: any;
|
|
16
33
|
}
|
|
17
34
|
export interface LinkAuthResult {
|
|
35
|
+
/** Whether authentication was successful */
|
|
18
36
|
authenticated: boolean;
|
|
37
|
+
/** Authentication credential if successful */
|
|
19
38
|
credential?: string;
|
|
39
|
+
/** Session info for subsequent requests */
|
|
20
40
|
session?: any;
|
|
41
|
+
/** Error message if authentication failed */
|
|
21
42
|
error?: string;
|
|
22
43
|
}
|
|
23
44
|
export declare class LinkHandler implements StrategyHandler {
|
|
@@ -27,13 +48,42 @@ export declare class LinkHandler implements StrategyHandler {
|
|
|
27
48
|
private onCancel?;
|
|
28
49
|
private pollingReject?;
|
|
29
50
|
private isPollingInProgress;
|
|
51
|
+
/**
|
|
52
|
+
* Invoke link-based authentication
|
|
53
|
+
* Opens authentication app while keeping user on current page
|
|
54
|
+
*/
|
|
30
55
|
invoke(data: PrepareResponse, options?: LinkAuthOptions): Promise<LinkAuthResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Open authentication link without navigating away
|
|
58
|
+
*/
|
|
31
59
|
private openAuthenticationLink;
|
|
60
|
+
/**
|
|
61
|
+
* Start polling for authentication status with constant interval
|
|
62
|
+
*/
|
|
32
63
|
private startPolling;
|
|
64
|
+
/**
|
|
65
|
+
* Stop polling
|
|
66
|
+
*/
|
|
33
67
|
private stopPolling;
|
|
68
|
+
/**
|
|
69
|
+
* Format response for backend processing
|
|
70
|
+
*/
|
|
34
71
|
formatResponse(response: LinkAuthResult): any;
|
|
72
|
+
/**
|
|
73
|
+
* Check if link strategy is supported
|
|
74
|
+
* Returns true for mobile devices (iOS and Android)
|
|
75
|
+
*/
|
|
35
76
|
isSupported(): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Clean up resources (stop polling if active)
|
|
79
|
+
*/
|
|
36
80
|
cleanup(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Check if polling is currently active
|
|
83
|
+
*/
|
|
37
84
|
isPolling(): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Cancel the ongoing authentication
|
|
87
|
+
*/
|
|
38
88
|
cancel(): void;
|
|
39
89
|
}
|