@glideidentity/web-client-sdk 5.0.1 → 5.1.1-beta.3
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 -6
- 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 +312 -2
- 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
|
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.PhoneAuthClient = void 0;
|
|
46
|
+
// Import API types for API communication
|
|
46
47
|
const API = __importStar(require("./api-types"));
|
|
47
48
|
const types_1 = require("./types");
|
|
48
49
|
const error_utils_1 = require("./error-utils");
|
|
@@ -57,31 +58,41 @@ class PhoneAuthClient {
|
|
|
57
58
|
this.crossDeviceActive = false;
|
|
58
59
|
this.retryCount = 0;
|
|
59
60
|
this.sessionCache = new Map();
|
|
61
|
+
// Store base timeout for normal operations
|
|
60
62
|
this.baseTimeout = config.timeout || 30000;
|
|
63
|
+
// Default configuration with cross-device support
|
|
61
64
|
this.config = {
|
|
62
65
|
endpoints: {
|
|
63
66
|
prepare: ((_a = config.endpoints) === null || _a === void 0 ? void 0 : _a.prepare) || '/api/magic-auth/prepare',
|
|
64
67
|
process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process',
|
|
65
|
-
polling: (_c = config.endpoints) === null || _c === void 0 ? void 0 : _c.polling
|
|
68
|
+
polling: (_c = config.endpoints) === null || _c === void 0 ? void 0 : _c.polling // Pass through the polling endpoint
|
|
66
69
|
},
|
|
67
70
|
timeout: config.timeout || 30000,
|
|
68
|
-
pollingInterval: config.pollingInterval || 2000,
|
|
69
|
-
maxPollingAttempts: config.maxPollingAttempts || 30,
|
|
71
|
+
pollingInterval: config.pollingInterval || 2000, // Default 2 seconds
|
|
72
|
+
maxPollingAttempts: config.maxPollingAttempts || 30, // Changed from 150 to 30 (1 minute total)
|
|
70
73
|
debug: config.debug || false,
|
|
71
74
|
aggregatorId: config.aggregatorId || 'default',
|
|
75
|
+
devEnv: config.devEnv,
|
|
72
76
|
devtools: config.devtools
|
|
73
77
|
};
|
|
74
78
|
this.debug = this.config.debug;
|
|
79
|
+
// Log initialization with devEnv if set
|
|
80
|
+
if (config.devEnv) {
|
|
81
|
+
console.log(`[PhoneAuth] Initialized with devEnv: ${config.devEnv}`);
|
|
82
|
+
}
|
|
83
|
+
// Store callbacks
|
|
75
84
|
this.callbacks = {
|
|
76
85
|
onCrossDeviceDetected: config.onCrossDeviceDetected,
|
|
77
86
|
onRetryAttempt: config.onRetryAttempt
|
|
78
87
|
};
|
|
88
|
+
// Initialize logger based on config
|
|
79
89
|
this.logger = logger_1.LoggerFactory.create({
|
|
80
90
|
level: config.logLevel,
|
|
81
91
|
prefix: '[PhoneAuth]',
|
|
82
92
|
remote: config.remoteLogging,
|
|
83
93
|
custom: config.logger
|
|
84
94
|
});
|
|
95
|
+
// Initialize developer tools if configured
|
|
85
96
|
if (((_d = config.devtools) === null || _d === void 0 ? void 0 : _d.showMobileConsole) && typeof window !== 'undefined') {
|
|
86
97
|
Promise.resolve().then(() => __importStar(require('./ui/mobile-debug-console'))).then(({ MobileDebugConsole }) => {
|
|
87
98
|
MobileDebugConsole.init();
|
|
@@ -90,21 +101,32 @@ class PhoneAuthClient {
|
|
|
90
101
|
console.error('[PhoneAuth] Failed to load mobile debug console:', err);
|
|
91
102
|
});
|
|
92
103
|
}
|
|
104
|
+
// Set up session cache cleanup
|
|
93
105
|
this.setupCacheCleanup();
|
|
94
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Get user-friendly error message using error utilities
|
|
109
|
+
*/
|
|
95
110
|
getUserFriendlyMessage(error) {
|
|
96
111
|
if (typeof error === 'string') {
|
|
112
|
+
// For legacy string error codes
|
|
97
113
|
return (0, error_utils_1.getUserMessage)({ code: error });
|
|
98
114
|
}
|
|
99
115
|
return (0, error_utils_1.getUserMessage)(error);
|
|
100
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Log error with proper context and sanitization
|
|
119
|
+
*/
|
|
101
120
|
logError(error, context) {
|
|
121
|
+
// Create breadcrumb for error tracking
|
|
102
122
|
const breadcrumb = (0, error_utils_1.createErrorBreadcrumb)(error);
|
|
123
|
+
// Serialize error for logging (sanitized)
|
|
103
124
|
const serialized = (0, error_utils_1.serializeError)(error);
|
|
104
125
|
if (this.debug || !(0, error_utils_1.isUserError)(error)) {
|
|
105
126
|
console.error('[PhoneAuth] Error:', Object.assign(Object.assign({}, serialized), { breadcrumb,
|
|
106
127
|
context }));
|
|
107
128
|
}
|
|
129
|
+
// Log trace context for distributed tracing (if available)
|
|
108
130
|
if (error.traceId) {
|
|
109
131
|
console.debug('[PhoneAuth] Trace Context:', {
|
|
110
132
|
traceId: error.traceId,
|
|
@@ -113,11 +135,20 @@ class PhoneAuthClient {
|
|
|
113
135
|
});
|
|
114
136
|
}
|
|
115
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if the browser supports secure phone authentication
|
|
140
|
+
*/
|
|
116
141
|
isSupported() {
|
|
142
|
+
// Only check on client side
|
|
117
143
|
if (typeof window === 'undefined')
|
|
118
144
|
return false;
|
|
145
|
+
// Check for the DigitalCredential constructor specifically
|
|
146
|
+
// This is more accurate than checking credentials.get which exists for other credential types
|
|
119
147
|
return 'DigitalCredential' in window;
|
|
120
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Get detailed browser support information
|
|
151
|
+
*/
|
|
121
152
|
getBrowserSupportInfo() {
|
|
122
153
|
if (typeof window === 'undefined') {
|
|
123
154
|
return {
|
|
@@ -136,6 +167,7 @@ class PhoneAuthClient {
|
|
|
136
167
|
browser: isChrome ? types_1.BrowserName.CHROME : isEdge ? types_1.BrowserName.EDGE : types_1.BrowserName.OTHER
|
|
137
168
|
};
|
|
138
169
|
}
|
|
170
|
+
// Provide specific guidance based on browser
|
|
139
171
|
if (isChrome || isEdge) {
|
|
140
172
|
return {
|
|
141
173
|
supported: false,
|
|
@@ -152,11 +184,16 @@ class PhoneAuthClient {
|
|
|
152
184
|
message: 'Your browser doesn\'t support the Digital Credentials API. Please use Chrome or Edge with the #web-identity-digital-credentials flag enabled.'
|
|
153
185
|
};
|
|
154
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Main verification method with silent retry support
|
|
189
|
+
*/
|
|
155
190
|
verify(options) {
|
|
156
191
|
return __awaiter(this, void 0, void 0, function* () {
|
|
192
|
+
// Reset retry count for new verification
|
|
157
193
|
this.retryCount = 0;
|
|
158
194
|
this.lastRequest = options;
|
|
159
|
-
const maxRetries = 2;
|
|
195
|
+
const maxRetries = 2; // Default max retries
|
|
196
|
+
// Try verification with silent retries
|
|
160
197
|
return this.verifyWithRetry(options, maxRetries);
|
|
161
198
|
});
|
|
162
199
|
}
|
|
@@ -164,38 +201,55 @@ class PhoneAuthClient {
|
|
|
164
201
|
return __awaiter(this, void 0, void 0, function* () {
|
|
165
202
|
var _a, _b;
|
|
166
203
|
try {
|
|
204
|
+
// Step 1: Prepare the phone verification request
|
|
167
205
|
const preparedRequest = yield this.preparePhoneRequest(options);
|
|
206
|
+
// Step 2: Invoke secure prompt for user consent (always in UI mode for high-level API)
|
|
168
207
|
const credentialResponse = yield this.invokeSecurePrompt(preparedRequest);
|
|
208
|
+
// Check if headless result was returned (this shouldn't happen in high-level API)
|
|
169
209
|
if (credentialResponse && typeof credentialResponse === 'object' && 'strategy' in credentialResponse) {
|
|
170
210
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_RESPONSE, 'Headless mode is not supported in authenticatePhoneNumber. Use preparePhoneRequest and invokeSecurePrompt directly for headless mode.');
|
|
171
211
|
}
|
|
212
|
+
// Step 3: Process the response through appropriate endpoint
|
|
172
213
|
const credential = credentialResponse;
|
|
214
|
+
// Validate use_case is provided for endpoint selection
|
|
173
215
|
if (!options.use_case) {
|
|
174
216
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
175
217
|
}
|
|
176
218
|
const result = options.use_case === API.USE_CASE.GET_PHONE_NUMBER
|
|
177
219
|
? yield this.getPhoneNumber(credential, preparedRequest.session)
|
|
178
220
|
: yield this.verifyPhoneNumber(credential, preparedRequest.session);
|
|
221
|
+
// Return the result directly - it's already the correct type
|
|
222
|
+
// Cache successful result with session info for later use
|
|
179
223
|
this.cacheSession(options, result);
|
|
180
224
|
return result;
|
|
181
225
|
}
|
|
182
226
|
catch (error) {
|
|
183
227
|
const authError = (0, error_utils_1.isPhoneAuthError)(error) ? error : (0, error_utils_1.parseBackendError)(error);
|
|
228
|
+
// Check if we should retry (silent retry - don't throw yet)
|
|
229
|
+
// Note: We cannot automatically retry USER_DENIED errors because the Digital Credentials API
|
|
230
|
+
// requires user interaction (transient activation). Automatic retries would fail with
|
|
231
|
+
// "The 'digital-credentials-get' feature requires transient activation" error.
|
|
184
232
|
if (this.shouldRetry(authError) && this.retryCount < maxRetries) {
|
|
185
233
|
this.retryCount++;
|
|
234
|
+
// Notify about retry attempt (but don't show error to user)
|
|
186
235
|
(_b = (_a = this.callbacks).onRetryAttempt) === null || _b === void 0 ? void 0 : _b.call(_a, this.retryCount, maxRetries);
|
|
187
236
|
if (this.debug) {
|
|
188
237
|
console.log(`[PhoneAuth] Retrying verification (attempt ${this.retryCount + 1}/${maxRetries + 1})`);
|
|
189
238
|
}
|
|
190
|
-
|
|
239
|
+
// Wait before retry
|
|
240
|
+
yield this.delay(Math.min(1000 * Math.pow(2, this.retryCount - 1), 5000)); // Exponential backoff
|
|
241
|
+
// Check cache for recent successful session
|
|
191
242
|
const cachedResult = this.getCachedSession(options);
|
|
192
243
|
if (cachedResult) {
|
|
193
244
|
if (this.debug)
|
|
194
245
|
console.log('[PhoneAuth] Using cached session result');
|
|
195
246
|
return cachedResult;
|
|
196
247
|
}
|
|
248
|
+
// Retry the verification
|
|
197
249
|
return this.verifyWithRetry(options, maxRetries);
|
|
198
250
|
}
|
|
251
|
+
// All retries exhausted or non-retryable error - now throw
|
|
252
|
+
// Add context
|
|
199
253
|
authError.context = {
|
|
200
254
|
step: 'complete',
|
|
201
255
|
useCase: options.use_case,
|
|
@@ -204,11 +258,15 @@ class PhoneAuthClient {
|
|
|
204
258
|
attemptNumber: this.retryCount + 1,
|
|
205
259
|
maxAttempts: maxRetries + 1
|
|
206
260
|
};
|
|
261
|
+
// Log error with proper sanitization
|
|
207
262
|
this.logError(authError, { options });
|
|
263
|
+
// Re-throw the structured error
|
|
208
264
|
if ((0, error_utils_1.isPhoneAuthError)(error)) {
|
|
265
|
+
// If it already has context, throw as-is
|
|
209
266
|
if (error.context) {
|
|
210
267
|
throw error;
|
|
211
268
|
}
|
|
269
|
+
// Otherwise, create a new error with context
|
|
212
270
|
const enhancedError = {
|
|
213
271
|
code: authError.code,
|
|
214
272
|
message: error.message,
|
|
@@ -233,35 +291,60 @@ class PhoneAuthClient {
|
|
|
233
291
|
}
|
|
234
292
|
});
|
|
235
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* High-level method to get phone number (complete flow)
|
|
296
|
+
* Handles prepare, credential prompt, and get phone number in one call
|
|
297
|
+
*/
|
|
236
298
|
getPhoneNumberComplete(options) {
|
|
237
299
|
return __awaiter(this, void 0, void 0, function* () {
|
|
238
300
|
return this.verify(Object.assign({ use_case: API.USE_CASE.GET_PHONE_NUMBER }, options));
|
|
239
301
|
});
|
|
240
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* High-level method to verify phone number (complete flow)
|
|
305
|
+
* Handles prepare, credential prompt, and verification in one call
|
|
306
|
+
*/
|
|
241
307
|
verifyPhoneNumberComplete(phoneNumber, options) {
|
|
242
308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
243
309
|
return this.verify(Object.assign({ use_case: API.USE_CASE.VERIFY_PHONE_NUMBER, phone_number: phoneNumber }, options));
|
|
244
310
|
});
|
|
245
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Step 1: Prepare phone verification request
|
|
314
|
+
*
|
|
315
|
+
* This method prepares a secure request for phone verification.
|
|
316
|
+
* You can use this with your own backend or the glide-sdk-node.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* const request = await phoneAuthClient.preparePhoneRequest({ useCase: 'GetPhoneNumber' });
|
|
321
|
+
* // Handle the request with custom logic
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
246
324
|
preparePhoneRequest(options) {
|
|
247
325
|
return __awaiter(this, void 0, void 0, function* () {
|
|
248
326
|
var _a, _b, _c;
|
|
327
|
+
// Validate phone number if provided
|
|
249
328
|
if (options.phone_number) {
|
|
250
329
|
const phoneValidation = (0, validation_utils_1.validatePhoneNumber)(options.phone_number);
|
|
251
330
|
if (!phoneValidation.valid) {
|
|
252
331
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_PHONE_NUMBER, phoneValidation.error, { field: 'phone_number' });
|
|
253
332
|
}
|
|
254
333
|
}
|
|
334
|
+
// Validate PLMN if provided
|
|
255
335
|
if (options.plmn) {
|
|
256
336
|
const plmnValidation = (0, validation_utils_1.validatePlmn)(options.plmn);
|
|
257
337
|
if (!plmnValidation.valid) {
|
|
258
338
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.BAD_REQUEST, plmnValidation.error, { field: 'plmn' });
|
|
259
339
|
}
|
|
260
340
|
}
|
|
341
|
+
// Validate use_case is provided (unless only parent_session_id is given)
|
|
261
342
|
if (!options.use_case && !(((_a = options.options) === null || _a === void 0 ? void 0 : _a.parent_session_id) && !options.phone_number && !options.plmn)) {
|
|
262
343
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
263
344
|
}
|
|
345
|
+
// Validate required parameters based on use case
|
|
264
346
|
if (!options.phone_number && !options.plmn && !((_b = options.options) === null || _b === void 0 ? void 0 : _b.parent_session_id)) {
|
|
347
|
+
// Provide specific error message based on use case
|
|
265
348
|
if (options.use_case === API.USE_CASE.GET_PHONE_NUMBER) {
|
|
266
349
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'PLMN (MCC/MNC) is required for GetPhoneNumber. Please provide carrier network information.', { field: 'plmn', useCase: 'GetPhoneNumber' });
|
|
267
350
|
}
|
|
@@ -269,36 +352,39 @@ class PhoneAuthClient {
|
|
|
269
352
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Phone number is required for VerifyPhoneNumber', { field: 'phoneNumber', useCase: 'VerifyPhoneNumber' });
|
|
270
353
|
}
|
|
271
354
|
else {
|
|
355
|
+
// Fallback for other use cases
|
|
272
356
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Either phone number or PLMN (MCC/MNC) must be provided', { field: 'phoneNumber,plmn' });
|
|
273
357
|
}
|
|
274
358
|
}
|
|
359
|
+
// Log parent session usage
|
|
275
360
|
if (((_c = options.options) === null || _c === void 0 ? void 0 : _c.parent_session_id) && !options.phone_number && !options.plmn) {
|
|
276
361
|
if (this.debug) {
|
|
277
362
|
console.log('[PhoneAuth] Using parent_session_id: %s, use_case: %s', options.options.parent_session_id, options.use_case || 'not provided');
|
|
278
363
|
}
|
|
279
364
|
}
|
|
280
365
|
const requestId = `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
.replace(/\//g, '_')
|
|
284
|
-
.replace(/=/g, '');
|
|
285
|
-
const nonceValidation = (0, validation_utils_1.validateNonce)(nonce);
|
|
286
|
-
if (!nonceValidation.valid) {
|
|
287
|
-
throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, 'Failed to generate valid nonce', { field: 'nonce' });
|
|
288
|
-
}
|
|
366
|
+
// Build properly typed request body according to API specification
|
|
367
|
+
// Be permissive - backend will ignore extra fields if not needed
|
|
289
368
|
const requestBody = {
|
|
369
|
+
// Include use_case if provided (optional when parent_session_id is given)
|
|
290
370
|
use_case: options.use_case,
|
|
371
|
+
// Include phone_number if provided (backend ignores if not needed)
|
|
291
372
|
phone_number: options.phone_number,
|
|
373
|
+
// Include PLMN if provided
|
|
292
374
|
plmn: options.plmn ? {
|
|
293
375
|
mcc: options.plmn.mcc,
|
|
294
376
|
mnc: options.plmn.mnc
|
|
295
377
|
} : undefined,
|
|
296
|
-
|
|
378
|
+
// Auto-generated request ID
|
|
297
379
|
id: requestId,
|
|
380
|
+
// Optional fields
|
|
298
381
|
client_info: {
|
|
299
382
|
user_agent: navigator.userAgent,
|
|
300
|
-
platform: navigator.platform
|
|
383
|
+
platform: navigator.platform,
|
|
384
|
+
// user_agent: "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
|
|
385
|
+
// platform: "Android"
|
|
301
386
|
},
|
|
387
|
+
// Advanced options (for desktop-mobile binding and future features)
|
|
302
388
|
options: options.options
|
|
303
389
|
};
|
|
304
390
|
this.log('Preparing phone verification request', requestBody);
|
|
@@ -309,15 +395,20 @@ class PhoneAuthClient {
|
|
|
309
395
|
body: JSON.stringify(requestBody)
|
|
310
396
|
});
|
|
311
397
|
if (!response.ok) {
|
|
398
|
+
// Try to get error details from response body
|
|
312
399
|
let errorDetails = null;
|
|
313
400
|
try {
|
|
314
401
|
errorDetails = yield response.json();
|
|
402
|
+
// Always include the HTTP status from the response
|
|
315
403
|
errorDetails = Object.assign(Object.assign({}, errorDetails), { status: response.status });
|
|
316
404
|
}
|
|
317
405
|
catch (_d) {
|
|
406
|
+
// If JSON parsing fails, use status text
|
|
318
407
|
errorDetails = { status: response.status, statusText: response.statusText };
|
|
319
408
|
}
|
|
409
|
+
// Parse the backend error response (handles both structured and unstructured errors)
|
|
320
410
|
const parsedError = (0, error_utils_1.parseBackendError)(errorDetails);
|
|
411
|
+
// Enhance with additional context
|
|
321
412
|
parsedError.context = {
|
|
322
413
|
step: 'prepare',
|
|
323
414
|
useCase: options.use_case,
|
|
@@ -325,6 +416,7 @@ class PhoneAuthClient {
|
|
|
325
416
|
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
|
|
326
417
|
url: typeof window !== 'undefined' ? window.location.href : undefined
|
|
327
418
|
};
|
|
419
|
+
// Add endpoint info
|
|
328
420
|
parsedError.details = Object.assign(Object.assign({}, parsedError.details), { endpoint: 'prepare', status: response.status });
|
|
329
421
|
throw parsedError;
|
|
330
422
|
}
|
|
@@ -333,12 +425,16 @@ class PhoneAuthClient {
|
|
|
333
425
|
if (!data.authentication_strategy || !data.data || !data.session) {
|
|
334
426
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_RESPONSE, 'Invalid response format from backend');
|
|
335
427
|
}
|
|
428
|
+
// Return the full response as-is
|
|
429
|
+
// The invoke method will handle it based on authentication_strategy
|
|
336
430
|
return data;
|
|
337
431
|
}
|
|
338
432
|
catch (error) {
|
|
433
|
+
// If it's already an AuthError, re-throw it
|
|
339
434
|
if (this.isAuthError(error)) {
|
|
340
435
|
throw error;
|
|
341
436
|
}
|
|
437
|
+
// Otherwise, wrap it as a network error
|
|
342
438
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Failed to prepare verification request', {
|
|
343
439
|
originalError: error,
|
|
344
440
|
context: {
|
|
@@ -352,16 +448,68 @@ class PhoneAuthClient {
|
|
|
352
448
|
}
|
|
353
449
|
});
|
|
354
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Step 2: Invoke secure prompt for user consent
|
|
453
|
+
*
|
|
454
|
+
* This method can work in two modes:
|
|
455
|
+
* 1. **UI Mode (default)**: Shows built-in UI components (modals/buttons)
|
|
456
|
+
* 2. **Headless Mode**: Returns raw data for custom UI implementation
|
|
457
|
+
*
|
|
458
|
+
* **Important**: This method automatically handles reactive objects from frameworks
|
|
459
|
+
* like Vue.js and React by deep cloning the input. This ensures compatibility with
|
|
460
|
+
* browser APIs that expect plain objects.
|
|
461
|
+
*
|
|
462
|
+
* @example UI Mode (shows modal/button)
|
|
463
|
+
* ```typescript
|
|
464
|
+
* // Shows SDK's built-in UI
|
|
465
|
+
* const credential = await phoneAuth.invokeSecurePrompt(prepareResult);
|
|
466
|
+
*
|
|
467
|
+
* // Customize the UI
|
|
468
|
+
* const credential = await phoneAuth.invokeSecurePrompt(prepareResult, {
|
|
469
|
+
* modalOptions: {
|
|
470
|
+
* title: 'Verify Your Identity',
|
|
471
|
+
* buttonText: 'Continue with Verizon'
|
|
472
|
+
* }
|
|
473
|
+
* });
|
|
474
|
+
* ```
|
|
475
|
+
*
|
|
476
|
+
* @example Extended Mode (returns control methods)
|
|
477
|
+
* ```typescript
|
|
478
|
+
* // Get control methods for custom implementation
|
|
479
|
+
* const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
|
|
480
|
+
* executionMode: 'extended',
|
|
481
|
+
* preventDefaultUI: true // Desktop: no modal
|
|
482
|
+
* });
|
|
483
|
+
*
|
|
484
|
+
* if (result.strategy === 'desktop') {
|
|
485
|
+
* // Show custom QR UI
|
|
486
|
+
* showCustomQR(result.qr_code_data);
|
|
487
|
+
* // Start polling
|
|
488
|
+
* await result.start_polling();
|
|
489
|
+
* }
|
|
490
|
+
* ```
|
|
491
|
+
*
|
|
492
|
+
* @param prepareResponse - Response from prepare() with strategy and data
|
|
493
|
+
* @param options - Control UI behavior and response type
|
|
494
|
+
* @returns Credential or ExtendedResponse based on executionMode
|
|
495
|
+
*/
|
|
355
496
|
invokeSecurePrompt(prepareResponse, options) {
|
|
356
497
|
return __awaiter(this, void 0, void 0, function* () {
|
|
498
|
+
// Deep clone to avoid issues with reactive objects (Vue/React)
|
|
499
|
+
// This ensures we work with plain objects for browser APIs
|
|
500
|
+
// Vue's reactivity system wraps objects in Proxies which can interfere
|
|
501
|
+
// with browser APIs like Digital Credentials API that expect plain objects
|
|
357
502
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
503
|
+
// Try structuredClone first (modern browsers), but catch errors and fallback to JSON method
|
|
358
504
|
let plainResponse;
|
|
359
505
|
try {
|
|
506
|
+
// structuredClone might throw if object contains non-cloneable properties
|
|
360
507
|
plainResponse = typeof structuredClone !== 'undefined'
|
|
361
508
|
? structuredClone(prepareResponse)
|
|
362
509
|
: JSON.parse(JSON.stringify(prepareResponse));
|
|
363
510
|
}
|
|
364
511
|
catch (cloneError) {
|
|
512
|
+
// Fallback to JSON method if structuredClone fails
|
|
365
513
|
if (this.debug) {
|
|
366
514
|
console.log('[PhoneAuth] structuredClone failed, using JSON fallback:', cloneError);
|
|
367
515
|
}
|
|
@@ -371,11 +519,16 @@ class PhoneAuthClient {
|
|
|
371
519
|
console.log('[PhoneAuth] Session cache size:', this.sessionCache.size);
|
|
372
520
|
console.log('[PhoneAuth] Retry count:', this.retryCount);
|
|
373
521
|
console.log('[PhoneAuth] PrepareResponse received:', JSON.stringify(plainResponse, null, 2));
|
|
522
|
+
// Treat options as InvokeOptions (the modern format)
|
|
523
|
+
// Legacy DesktopAuthOptions properties will still work through property access
|
|
374
524
|
const opts = options;
|
|
525
|
+
// Get configuration from options - access properties directly
|
|
375
526
|
const strategy = plainResponse.authentication_strategy;
|
|
376
527
|
const preventDefaultUI = (_a = opts === null || opts === void 0 ? void 0 : opts.preventDefaultUI) !== null && _a !== void 0 ? _a : false;
|
|
377
528
|
const executionMode = (_b = opts === null || opts === void 0 ? void 0 : opts.executionMode) !== null && _b !== void 0 ? _b : 'standard';
|
|
529
|
+
// Handle based on authentication strategy
|
|
378
530
|
if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.TS43) {
|
|
531
|
+
// Check browser support for TS43 strategy which requires Digital Credentials API
|
|
379
532
|
if (!this.isSupported()) {
|
|
380
533
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.BROWSER_NOT_SUPPORTED, 'Your browser does not support the Digital Credentials API required for TS43 authentication');
|
|
381
534
|
}
|
|
@@ -389,13 +542,18 @@ class PhoneAuthClient {
|
|
|
389
542
|
}
|
|
390
543
|
};
|
|
391
544
|
this.log('Invoking TS43 secure authentication prompt', secureCredentialRequest);
|
|
545
|
+
// Function to trigger TS43 authentication
|
|
392
546
|
const triggerTS43 = () => __awaiter(this, void 0, void 0, function* () {
|
|
393
547
|
var _a, _b;
|
|
394
548
|
try {
|
|
549
|
+
// This is the browser API call for TS43
|
|
550
|
+
// Cast to CredentialRequestOptions with digital field (TS43 specific)
|
|
395
551
|
const credentialOptions = secureCredentialRequest;
|
|
396
552
|
const credentialResponse = yield navigator.credentials.get(credentialOptions);
|
|
553
|
+
// Type guard for Digital Credential response
|
|
397
554
|
const digitalResponse = credentialResponse;
|
|
398
555
|
if (!digitalResponse || !('data' in digitalResponse) || !digitalResponse.data) {
|
|
556
|
+
// Check if this is likely due to the browser flag being disabled
|
|
399
557
|
const supportInfo = this.getBrowserSupportInfo();
|
|
400
558
|
if (supportInfo.browser === types_1.BrowserName.CHROME || supportInfo.browser === types_1.BrowserName.EDGE) {
|
|
401
559
|
throw new Error(`Digital Credentials API returned no response. This usually means the browser feature flag is not enabled. Please ensure the ${supportInfo.helpUrl || '#web-identity-digital-credentials flag'} is set to "Enabled" (not "Default" or "Disabled") and restart your browser.`);
|
|
@@ -407,6 +565,7 @@ class PhoneAuthClient {
|
|
|
407
565
|
return credentialData.vp_token;
|
|
408
566
|
}
|
|
409
567
|
catch (error) {
|
|
568
|
+
// Capture detailed browser error information
|
|
410
569
|
const errorObj = error;
|
|
411
570
|
const browserErrorDetails = {
|
|
412
571
|
name: errorObj.name || 'UnknownError',
|
|
@@ -419,9 +578,11 @@ class PhoneAuthClient {
|
|
|
419
578
|
timestamp: new Date().toISOString(),
|
|
420
579
|
userAgent: navigator.userAgent,
|
|
421
580
|
url: window.location.href,
|
|
581
|
+
// Include request details for debugging
|
|
422
582
|
authentication_strategy: plainResponse.authentication_strategy,
|
|
423
583
|
hasSession: !!plainResponse.session
|
|
424
584
|
};
|
|
585
|
+
// Handle specific browser errors
|
|
425
586
|
if (errorObj.name === types_1.BrowserError.NOT_ALLOWED) {
|
|
426
587
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'User denied the credential request or the request timed out', {
|
|
427
588
|
originalError: error,
|
|
@@ -429,6 +590,7 @@ class PhoneAuthClient {
|
|
|
429
590
|
context: errorContext
|
|
430
591
|
});
|
|
431
592
|
}
|
|
593
|
+
// NetworkError with code 19 specifically indicates user cancellation in Digital Credentials API
|
|
432
594
|
if (errorObj.name === types_1.BrowserError.NETWORK && errorObj.code === types_1.BrowserErrorCode.USER_CANCELLED_DC_API) {
|
|
433
595
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled by user', {
|
|
434
596
|
originalError: error,
|
|
@@ -436,6 +598,7 @@ class PhoneAuthClient {
|
|
|
436
598
|
context: errorContext
|
|
437
599
|
});
|
|
438
600
|
}
|
|
601
|
+
// NetworkError without code 19 is a real network error
|
|
439
602
|
if (errorObj.name === types_1.BrowserError.NETWORK) {
|
|
440
603
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Network error occurred while retrieving credentials', {
|
|
441
604
|
originalError: error,
|
|
@@ -457,6 +620,7 @@ class PhoneAuthClient {
|
|
|
457
620
|
context: errorContext
|
|
458
621
|
});
|
|
459
622
|
}
|
|
623
|
+
// Check for other cancellation patterns
|
|
460
624
|
if (errorObj.name === types_1.BrowserError.ABORT ||
|
|
461
625
|
((_a = browserErrorDetails.message) === null || _a === void 0 ? void 0 : _a.includes('The operation was aborted')) ||
|
|
462
626
|
((_b = browserErrorDetails.message) === null || _b === void 0 ? void 0 : _b.includes('User cancelled'))) {
|
|
@@ -466,6 +630,7 @@ class PhoneAuthClient {
|
|
|
466
630
|
context: errorContext
|
|
467
631
|
});
|
|
468
632
|
}
|
|
633
|
+
// For any other errors, capture all details
|
|
469
634
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Digital Credentials API error: ${errorObj.message || 'Unknown error'}`, {
|
|
470
635
|
originalError: error,
|
|
471
636
|
browserError: browserErrorDetails,
|
|
@@ -473,6 +638,11 @@ class PhoneAuthClient {
|
|
|
473
638
|
});
|
|
474
639
|
}
|
|
475
640
|
});
|
|
641
|
+
// IMPORTANT: For TS43, we ALWAYS call the API directly without any modal
|
|
642
|
+
// The Digital Credentials API provides its own OS-level UI (drawer/bottom sheet)
|
|
643
|
+
// Adding our own modal would be redundant and confusing for users
|
|
644
|
+
// Unlike Link (which needs a button for iOS App Clips), TS43 just needs direct invocation
|
|
645
|
+
// Enhanced trigger function with callback support
|
|
476
646
|
const enhancedTriggerTS43 = () => __awaiter(this, void 0, void 0, function* () {
|
|
477
647
|
var _a, _b;
|
|
478
648
|
try {
|
|
@@ -492,11 +662,16 @@ class PhoneAuthClient {
|
|
|
492
662
|
throw error;
|
|
493
663
|
}
|
|
494
664
|
});
|
|
665
|
+
// TS43 always auto-triggers (no SDK UI ever)
|
|
666
|
+
// The Digital Credentials API provides its own OS-level UI
|
|
667
|
+
// Use a wrapper object to allow updating the promise reference
|
|
495
668
|
const credentialWrapper = {
|
|
496
669
|
promise: null
|
|
497
670
|
};
|
|
498
671
|
try {
|
|
672
|
+
// Always try to trigger immediately
|
|
499
673
|
const vpToken = yield enhancedTriggerTS43();
|
|
674
|
+
// Convert to AuthCredential format
|
|
500
675
|
credentialWrapper.promise = Promise.resolve({
|
|
501
676
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
502
677
|
session: plainResponse.session,
|
|
@@ -504,25 +679,34 @@ class PhoneAuthClient {
|
|
|
504
679
|
});
|
|
505
680
|
}
|
|
506
681
|
catch (error) {
|
|
682
|
+
// If auto-trigger fails, create a rejected promise
|
|
507
683
|
credentialWrapper.promise = Promise.reject(error);
|
|
508
684
|
}
|
|
685
|
+
// Handle based on execution mode
|
|
509
686
|
if (executionMode === 'extended') {
|
|
687
|
+
// Extended mode - return control methods
|
|
510
688
|
const response = {
|
|
511
689
|
strategy: 'ts43',
|
|
512
690
|
session: plainResponse.session,
|
|
513
|
-
credential: credentialWrapper.promise,
|
|
691
|
+
credential: credentialWrapper.promise, // Initial value
|
|
692
|
+
// Re-trigger credential request
|
|
514
693
|
trigger: () => __awaiter(this, void 0, void 0, function* () {
|
|
515
694
|
const vpToken = yield enhancedTriggerTS43();
|
|
695
|
+
// Update the credential promise in the wrapper
|
|
516
696
|
credentialWrapper.promise = Promise.resolve({
|
|
517
697
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
518
698
|
session: plainResponse.session,
|
|
519
699
|
authenticated: true
|
|
520
700
|
});
|
|
701
|
+
// Return void as per interface
|
|
521
702
|
}),
|
|
522
703
|
cancel: () => {
|
|
704
|
+
// TS43 doesn't have a way to cancel once triggered
|
|
705
|
+
// but we can reject the promise
|
|
523
706
|
credentialWrapper.promise = Promise.reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
|
|
524
707
|
}
|
|
525
708
|
};
|
|
709
|
+
// Define credential as a getter that always returns the current promise
|
|
526
710
|
Object.defineProperty(response, 'credential', {
|
|
527
711
|
get() {
|
|
528
712
|
return credentialWrapper.promise;
|
|
@@ -533,15 +717,20 @@ class PhoneAuthClient {
|
|
|
533
717
|
return response;
|
|
534
718
|
}
|
|
535
719
|
else {
|
|
720
|
+
// Standard mode - just return credential
|
|
721
|
+
// Wait for and return the credential
|
|
536
722
|
const credential = yield credentialWrapper.promise;
|
|
723
|
+
// Return in standard format
|
|
537
724
|
return {
|
|
538
725
|
[plainResponse.session.session_key]: credential.credential
|
|
539
726
|
};
|
|
540
727
|
}
|
|
541
728
|
}
|
|
542
729
|
else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.DESKTOP) {
|
|
730
|
+
// Desktop strategy - QR code based authentication
|
|
543
731
|
const desktopData = plainResponse.data;
|
|
544
732
|
const handler = new desktop_1.DesktopHandler();
|
|
733
|
+
// Extract QR code data - convert to QRCodeData format for modal
|
|
545
734
|
const qrCodeData = {
|
|
546
735
|
iosQRCode: ((_c = desktopData.data) === null || _c === void 0 ? void 0 : _c.ios_qr_image) || desktopData.ios_qr_image ||
|
|
547
736
|
((_d = desktopData.data) === null || _d === void 0 ? void 0 : _d.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code || '',
|
|
@@ -549,15 +738,18 @@ class PhoneAuthClient {
|
|
|
549
738
|
iosUrl: ((_f = desktopData.data) === null || _f === void 0 ? void 0 : _f.ios_url) || desktopData.ios_url,
|
|
550
739
|
androidUrl: ((_g = desktopData.data) === null || _g === void 0 ? void 0 : _g.android_url) || desktopData.android_url
|
|
551
740
|
};
|
|
741
|
+
// Also keep snake_case format for extended response
|
|
552
742
|
const qrCodeDataSnakeCase = {
|
|
553
743
|
ios_qr_image: ((_h = desktopData.data) === null || _h === void 0 ? void 0 : _h.ios_qr_image) || desktopData.ios_qr_image,
|
|
554
744
|
android_qr_image: ((_j = desktopData.data) === null || _j === void 0 ? void 0 : _j.android_qr_image) || desktopData.android_qr_image,
|
|
555
745
|
qr_code: ((_k = desktopData.data) === null || _k === void 0 ? void 0 : _k.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code,
|
|
556
746
|
challenge: (_l = desktopData.data) === null || _l === void 0 ? void 0 : _l.challenge
|
|
557
747
|
};
|
|
748
|
+
// Polling options - gather from any options format
|
|
558
749
|
const pollingEndpointToUse = (opts === null || opts === void 0 ? void 0 : opts.pollingEndpoint) ||
|
|
559
750
|
((_m = this.config.endpoints) === null || _m === void 0 ? void 0 : _m.polling);
|
|
560
|
-
const pollingOptions = Object.assign(Object.assign({}, opts), { pollingEndpoint: pollingEndpointToUse, pollingInterval: (opts === null || opts === void 0 ? void 0 : opts.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (opts === null || opts === void 0 ? void 0 : opts.maxPollingAttempts) || this.config.maxPollingAttempts || 30, onQRCodeReady: undefined, onStatusUpdate: undefined });
|
|
751
|
+
const pollingOptions = Object.assign(Object.assign({}, opts), { pollingEndpoint: pollingEndpointToUse, pollingInterval: (opts === null || opts === void 0 ? void 0 : opts.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (opts === null || opts === void 0 ? void 0 : opts.maxPollingAttempts) || this.config.maxPollingAttempts || 30, devEnv: (opts === null || opts === void 0 ? void 0 : opts.devEnv) || this.config.devEnv, onQRCodeReady: undefined, onStatusUpdate: undefined });
|
|
752
|
+
// Decide whether to show modal based on preventDefaultUI
|
|
561
753
|
const showModal = !preventDefaultUI;
|
|
562
754
|
let modal;
|
|
563
755
|
let modalRef = undefined;
|
|
@@ -569,27 +761,40 @@ class PhoneAuthClient {
|
|
|
569
761
|
});
|
|
570
762
|
if (showModal) {
|
|
571
763
|
console.log('[Desktop] Creating modal with QR data:', qrCodeData);
|
|
764
|
+
// Create and setup modal
|
|
572
765
|
modal = new modal_1.AuthModal(opts === null || opts === void 0 ? void 0 : opts.modalOptions, opts === null || opts === void 0 ? void 0 : opts.callbacks);
|
|
573
766
|
modal.setCloseCallback(() => {
|
|
574
767
|
this.log('Desktop QR modal closed by user, cancelling polling');
|
|
575
768
|
handler.cancel();
|
|
576
769
|
});
|
|
770
|
+
// Add UI callbacks to polling options
|
|
577
771
|
pollingOptions.onQRCodeReady = (qrData) => {
|
|
578
772
|
console.log('[Desktop] onQRCodeReady callback triggered:', qrData);
|
|
579
773
|
modal.showQRCode(qrData, 'Scan with your mobile device');
|
|
580
774
|
};
|
|
581
775
|
pollingOptions.onStatusUpdate = (status) => {
|
|
582
|
-
if (status.status === '
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
776
|
+
if (status.status === 'pending') {
|
|
777
|
+
modal.updateStatus('Waiting for authentication...');
|
|
778
|
+
}
|
|
779
|
+
else if (status.status === 'authenticated') {
|
|
780
|
+
modal.updateStatus('Authentication successful!');
|
|
781
|
+
setTimeout(() => modal.close(), 1500);
|
|
782
|
+
}
|
|
783
|
+
else if (status.status === 'expired') {
|
|
784
|
+
modal.updateStatus('QR code expired', true);
|
|
785
|
+
}
|
|
786
|
+
else if (status.status === 'error') {
|
|
787
|
+
modal.updateStatus('Authentication failed', true);
|
|
586
788
|
}
|
|
587
789
|
};
|
|
790
|
+
// Note: We don't show the QR code here. It will be shown by the onQRCodeReady callback
|
|
791
|
+
// that gets triggered immediately when handler.invoke() is called
|
|
588
792
|
modalRef = modal;
|
|
589
793
|
}
|
|
590
794
|
else {
|
|
591
795
|
console.log('[Desktop] Modal not shown - preventDefaultUI is true');
|
|
592
796
|
}
|
|
797
|
+
// Create credential promise
|
|
593
798
|
const startPolling = () => handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
594
799
|
if (result.authenticated && result.credential) {
|
|
595
800
|
return {
|
|
@@ -602,13 +807,22 @@ class PhoneAuthClient {
|
|
|
602
807
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
|
|
603
808
|
}
|
|
604
809
|
});
|
|
810
|
+
// Handle based on execution mode
|
|
605
811
|
if (executionMode === 'extended') {
|
|
812
|
+
// Extended mode - return control methods
|
|
813
|
+
// Create a promise and always start polling immediately
|
|
606
814
|
const credentialPromise = new Promise((resolve, reject) => {
|
|
815
|
+
// Always start polling immediately in extended mode (consistent with Link)
|
|
816
|
+
// This prevents the "unresolved promise trap" where developers might await
|
|
817
|
+
// the credential before calling start_polling()
|
|
607
818
|
startPolling()
|
|
608
819
|
.then(resolve)
|
|
609
820
|
.catch(reject);
|
|
610
821
|
});
|
|
822
|
+
// Create wrapped functions
|
|
611
823
|
const wrappedStartPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
824
|
+
// Polling has already started automatically in extended mode
|
|
825
|
+
// This method just returns the existing credential promise for consistency
|
|
612
826
|
return credentialPromise;
|
|
613
827
|
});
|
|
614
828
|
const wrappedStopPolling = () => {
|
|
@@ -621,7 +835,9 @@ class PhoneAuthClient {
|
|
|
621
835
|
handler.cleanup();
|
|
622
836
|
if (modal)
|
|
623
837
|
modal.close();
|
|
838
|
+
// The credential promise will be rejected by the handler.cancel() call
|
|
624
839
|
};
|
|
840
|
+
// Create the response object with a getter for is_polling
|
|
625
841
|
const response = {
|
|
626
842
|
strategy: 'desktop',
|
|
627
843
|
session: plainResponse.session,
|
|
@@ -631,8 +847,10 @@ class PhoneAuthClient {
|
|
|
631
847
|
start_polling: wrappedStartPolling,
|
|
632
848
|
stop_polling: wrappedStopPolling,
|
|
633
849
|
cancel: wrappedCancel,
|
|
634
|
-
|
|
850
|
+
// This will be replaced with a getter
|
|
851
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
635
852
|
};
|
|
853
|
+
// Define is_polling as a getter that returns current state
|
|
636
854
|
Object.defineProperty(response, 'is_polling', {
|
|
637
855
|
get() {
|
|
638
856
|
return handler.isPolling();
|
|
@@ -643,8 +861,11 @@ class PhoneAuthClient {
|
|
|
643
861
|
return response;
|
|
644
862
|
}
|
|
645
863
|
else {
|
|
864
|
+
// Standard mode - return credential when complete
|
|
865
|
+
// Start polling and wait for result
|
|
646
866
|
try {
|
|
647
867
|
const credential = yield startPolling();
|
|
868
|
+
// Extract session ID for compatibility
|
|
648
869
|
let sessionId = 'default';
|
|
649
870
|
if (desktopData && typeof desktopData === 'object') {
|
|
650
871
|
if (desktopData.data && typeof desktopData.data === 'object') {
|
|
@@ -664,8 +885,10 @@ class PhoneAuthClient {
|
|
|
664
885
|
}
|
|
665
886
|
}
|
|
666
887
|
else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.LINK) {
|
|
888
|
+
// Link strategy - app-based authentication (iOS/Android)
|
|
667
889
|
const linkData = plainResponse.data;
|
|
668
890
|
const handler = new link_1.LinkHandler();
|
|
891
|
+
// Create reusable trigger function that ONLY opens the App Clip
|
|
669
892
|
const triggerLink = () => {
|
|
670
893
|
var _a, _b;
|
|
671
894
|
try {
|
|
@@ -685,9 +908,12 @@ class PhoneAuthClient {
|
|
|
685
908
|
});
|
|
686
909
|
}
|
|
687
910
|
};
|
|
911
|
+
// Link always auto-opens the app (no SDK UI by default)
|
|
912
|
+
// Open immediately to preserve user gesture context
|
|
688
913
|
if ((opts === null || opts === void 0 ? void 0 : opts.autoTrigger) !== false) {
|
|
689
914
|
triggerLink();
|
|
690
915
|
}
|
|
916
|
+
// Start polling in the background
|
|
691
917
|
console.log('[PhoneAuth Client] Link polling config:', {
|
|
692
918
|
fromOptions: opts === null || opts === void 0 ? void 0 : opts.pollingEndpoint,
|
|
693
919
|
fromClientConfig: (_o = this.config.endpoints) === null || _o === void 0 ? void 0 : _o.polling,
|
|
@@ -697,15 +923,20 @@ class PhoneAuthClient {
|
|
|
697
923
|
pollingEndpoint: (opts === null || opts === void 0 ? void 0 : opts.pollingEndpoint) || ((_q = this.config.endpoints) === null || _q === void 0 ? void 0 : _q.polling),
|
|
698
924
|
pollingInterval: (opts === null || opts === void 0 ? void 0 : opts.pollingInterval) || this.config.pollingInterval || 2000,
|
|
699
925
|
maxPollingAttempts: (opts === null || opts === void 0 ? void 0 : opts.maxPollingAttempts) || this.config.maxPollingAttempts || 30,
|
|
926
|
+
devEnv: (opts === null || opts === void 0 ? void 0 : opts.devEnv) || this.config.devEnv,
|
|
700
927
|
onLinkOpened: undefined,
|
|
701
928
|
onStatusUpdate: undefined
|
|
702
929
|
};
|
|
703
930
|
console.log('[PhoneAuth Client] Final Link polling options:', pollingOptions);
|
|
931
|
+
// Handle based on execution mode
|
|
704
932
|
if (executionMode === 'extended') {
|
|
933
|
+
// Extended mode - return control methods and start polling immediately
|
|
705
934
|
let pollingStarted = false;
|
|
706
935
|
let pollingPromise = null;
|
|
936
|
+
// Start polling immediately (Link always polls automatically unlike Desktop)
|
|
707
937
|
pollingPromise = handler.invoke(plainResponse, pollingOptions);
|
|
708
938
|
pollingStarted = true;
|
|
939
|
+
// Create credential promise from the polling result
|
|
709
940
|
const credentialPromise = pollingPromise.then(result => {
|
|
710
941
|
if (result.authenticated && result.credential) {
|
|
711
942
|
return {
|
|
@@ -718,8 +949,10 @@ class PhoneAuthClient {
|
|
|
718
949
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
719
950
|
}
|
|
720
951
|
});
|
|
952
|
+
// Function to restart polling (for retry scenarios)
|
|
721
953
|
const startPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
722
954
|
if (!pollingStarted) {
|
|
955
|
+
// This is here for API consistency, but for Link it's already polling
|
|
723
956
|
pollingStarted = true;
|
|
724
957
|
if (!pollingPromise) {
|
|
725
958
|
pollingPromise = handler.invoke(plainResponse, pollingOptions);
|
|
@@ -736,6 +969,7 @@ class PhoneAuthClient {
|
|
|
736
969
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
737
970
|
}
|
|
738
971
|
}
|
|
972
|
+
// If already polling, just return the existing promise result
|
|
739
973
|
const result = yield pollingPromise;
|
|
740
974
|
if (result.authenticated && result.credential) {
|
|
741
975
|
return {
|
|
@@ -755,15 +989,21 @@ class PhoneAuthClient {
|
|
|
755
989
|
data: {
|
|
756
990
|
app_url: linkData.url
|
|
757
991
|
},
|
|
992
|
+
// Re-open the app link
|
|
758
993
|
trigger: triggerLink,
|
|
994
|
+
// Polling control - now prevents double polling
|
|
759
995
|
start_polling: startPolling,
|
|
760
996
|
stop_polling: () => handler.cleanup(),
|
|
761
997
|
cancel: () => {
|
|
762
998
|
handler.cancel();
|
|
763
999
|
handler.cleanup();
|
|
1000
|
+
// Note: For Link, polling is already started, so the promise
|
|
1001
|
+
// will be rejected by the handler.cancel() call
|
|
764
1002
|
},
|
|
765
|
-
|
|
1003
|
+
// This will be replaced with a getter
|
|
1004
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
766
1005
|
};
|
|
1006
|
+
// Define is_polling as a getter that returns current state
|
|
767
1007
|
Object.defineProperty(response, 'is_polling', {
|
|
768
1008
|
get() {
|
|
769
1009
|
return handler.isPolling();
|
|
@@ -774,6 +1014,7 @@ class PhoneAuthClient {
|
|
|
774
1014
|
return response;
|
|
775
1015
|
}
|
|
776
1016
|
else {
|
|
1017
|
+
// Standard mode - start polling immediately and wait for completion
|
|
777
1018
|
const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
778
1019
|
if (result.authenticated && result.credential) {
|
|
779
1020
|
return {
|
|
@@ -786,28 +1027,44 @@ class PhoneAuthClient {
|
|
|
786
1027
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
787
1028
|
}
|
|
788
1029
|
});
|
|
1030
|
+
// Wait for credential and return in standard format
|
|
789
1031
|
const credential = yield credentialPromise;
|
|
790
1032
|
const aggregatorId = this.config.aggregatorId || 'default';
|
|
791
1033
|
return { [aggregatorId]: credential.credential };
|
|
792
1034
|
}
|
|
793
1035
|
}
|
|
794
1036
|
else {
|
|
1037
|
+
// Unknown strategy
|
|
795
1038
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.UNSUPPORTED_STRATEGY, `Unknown authentication strategy: ${plainResponse.authentication_strategy}`);
|
|
796
1039
|
}
|
|
797
1040
|
});
|
|
798
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Step 3A: Get phone number from credential
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* ```typescript
|
|
1047
|
+
* const prepareResp = await phoneAuthClient.preparePhoneRequest({ useCase: 'GetPhoneNumber', plmn: {...} });
|
|
1048
|
+
* const credential = await phoneAuthClient.invokeSecurePrompt(prepareResp);
|
|
1049
|
+
* const result = await phoneAuthClient.getPhoneNumber(credential, prepareResp.session);
|
|
1050
|
+
* console.log(result.phone_number); // +1234567890
|
|
1051
|
+
* ```
|
|
1052
|
+
*/
|
|
799
1053
|
getPhoneNumber(credentialResponse, session) {
|
|
800
1054
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1055
|
+
// Extract credential string
|
|
801
1056
|
const credentialString = this.extractCredentialString(credentialResponse);
|
|
1057
|
+
// Build request body for GetPhoneNumber
|
|
802
1058
|
const requestBody = {
|
|
803
1059
|
session: session,
|
|
804
1060
|
credential: credentialString,
|
|
805
|
-
use_case: API.USE_CASE.GET_PHONE_NUMBER
|
|
1061
|
+
use_case: API.USE_CASE.GET_PHONE_NUMBER // Required for server routing
|
|
806
1062
|
};
|
|
1063
|
+
// Only show full details in debug mode, mask sensitive data otherwise
|
|
807
1064
|
if (this.config.debug) {
|
|
808
1065
|
this.log('Getting phone number from credential', {
|
|
809
1066
|
session: session,
|
|
810
|
-
credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined',
|
|
1067
|
+
credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined', // Show partial for debugging
|
|
811
1068
|
endpoint: this.config.endpoints.process || '/api/phone-auth/process'
|
|
812
1069
|
});
|
|
813
1070
|
}
|
|
@@ -851,18 +1108,35 @@ class PhoneAuthClient {
|
|
|
851
1108
|
}
|
|
852
1109
|
});
|
|
853
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Step 3B: Verify phone number with credential
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```typescript
|
|
1116
|
+
* const prepareResp = await phoneAuthClient.preparePhoneRequest({
|
|
1117
|
+
* useCase: 'VerifyPhoneNumber',
|
|
1118
|
+
* phoneNumber: '+1234567890'
|
|
1119
|
+
* });
|
|
1120
|
+
* const credential = await phoneAuthClient.invokeSecurePrompt(prepareResp);
|
|
1121
|
+
* const result = await phoneAuthClient.verifyPhoneNumber(credential, prepareResp.session);
|
|
1122
|
+
* console.log(result.verified); // true
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
854
1125
|
verifyPhoneNumber(credentialResponse, session) {
|
|
855
1126
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1127
|
+
// Extract credential string
|
|
856
1128
|
const credentialString = this.extractCredentialString(credentialResponse);
|
|
1129
|
+
// Build request body for VerifyPhoneNumber
|
|
857
1130
|
const requestBody = {
|
|
858
1131
|
session: session,
|
|
859
1132
|
credential: credentialString,
|
|
860
|
-
use_case: API.USE_CASE.VERIFY_PHONE_NUMBER
|
|
1133
|
+
use_case: API.USE_CASE.VERIFY_PHONE_NUMBER // Required for server routing
|
|
861
1134
|
};
|
|
1135
|
+
// Only show full details in debug mode, mask sensitive data otherwise
|
|
862
1136
|
if (this.config.debug) {
|
|
863
1137
|
this.log('Verifying phone number with credential', {
|
|
864
1138
|
session: session,
|
|
865
|
-
credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined',
|
|
1139
|
+
credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined', // Show partial for debugging
|
|
866
1140
|
endpoint: this.config.endpoints.process || '/api/phone-auth/process'
|
|
867
1141
|
});
|
|
868
1142
|
}
|
|
@@ -909,36 +1183,53 @@ class PhoneAuthClient {
|
|
|
909
1183
|
}
|
|
910
1184
|
});
|
|
911
1185
|
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Helper to extract credential string from various formats
|
|
1188
|
+
*/
|
|
912
1189
|
extractCredentialString(credentialResponse) {
|
|
1190
|
+
// If already a string, return it
|
|
913
1191
|
if (typeof credentialResponse === 'string') {
|
|
914
1192
|
return credentialResponse;
|
|
915
1193
|
}
|
|
1194
|
+
// Extract from vp_token object - try configured aggregatorId first, then 'glide', then 'default'
|
|
916
1195
|
let credential = credentialResponse[this.config.aggregatorId || 'glide'];
|
|
1196
|
+
// Fallback to 'glide' if not found
|
|
917
1197
|
if (!credential && credentialResponse['glide']) {
|
|
918
1198
|
credential = credentialResponse['glide'];
|
|
919
1199
|
}
|
|
1200
|
+
// Fallback to 'default' if still not found
|
|
920
1201
|
if (!credential && credentialResponse['default']) {
|
|
921
1202
|
credential = credentialResponse['default'];
|
|
922
1203
|
}
|
|
1204
|
+
// If still not found, try to get the first available key
|
|
923
1205
|
if (!credential) {
|
|
924
1206
|
const keys = Object.keys(credentialResponse);
|
|
925
1207
|
if (keys.length > 0) {
|
|
926
1208
|
credential = credentialResponse[keys[0]];
|
|
927
1209
|
}
|
|
928
1210
|
}
|
|
1211
|
+
// Convert array to string if needed
|
|
929
1212
|
return Array.isArray(credential) ? credential[0] : credential;
|
|
930
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Helper to extract error details from response
|
|
1216
|
+
*/
|
|
931
1217
|
extractErrorDetails(response) {
|
|
932
1218
|
return __awaiter(this, void 0, void 0, function* () {
|
|
933
1219
|
try {
|
|
934
1220
|
const errorData = yield response.json();
|
|
935
|
-
|
|
1221
|
+
// Always include the HTTP status from the response
|
|
1222
|
+
return Object.assign(Object.assign({}, errorData), { status: response.status // Ensure HTTP status is included
|
|
1223
|
+
});
|
|
936
1224
|
}
|
|
937
1225
|
catch (_a) {
|
|
938
1226
|
return { status: response.status, statusText: response.statusText };
|
|
939
1227
|
}
|
|
940
1228
|
});
|
|
941
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Fetch with timeout
|
|
1232
|
+
*/
|
|
942
1233
|
fetchWithTimeout(url, options) {
|
|
943
1234
|
return __awaiter(this, void 0, void 0, function* () {
|
|
944
1235
|
const controller = new AbortController();
|
|
@@ -951,18 +1242,25 @@ class PhoneAuthClient {
|
|
|
951
1242
|
}
|
|
952
1243
|
});
|
|
953
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Create an AuthError
|
|
1247
|
+
*/
|
|
954
1248
|
createError(code, message, details) {
|
|
955
1249
|
const error = {
|
|
956
1250
|
code,
|
|
957
1251
|
message,
|
|
958
|
-
details: (details === null || details === void 0 ? void 0 : details.originalError) ? Object.assign(Object.assign({}, details), { originalError: undefined
|
|
1252
|
+
details: (details === null || details === void 0 ? void 0 : details.originalError) ? Object.assign(Object.assign({}, details), { originalError: undefined // Remove the original error object
|
|
1253
|
+
}) : details
|
|
959
1254
|
};
|
|
1255
|
+
// Add browser error details if present
|
|
960
1256
|
if (details === null || details === void 0 ? void 0 : details.browserError) {
|
|
961
1257
|
error.browserError = details.browserError;
|
|
962
1258
|
}
|
|
1259
|
+
// Add context if present
|
|
963
1260
|
if (details === null || details === void 0 ? void 0 : details.context) {
|
|
964
1261
|
error.context = details.context;
|
|
965
1262
|
}
|
|
1263
|
+
// Add other specific fields
|
|
966
1264
|
if (details === null || details === void 0 ? void 0 : details.status) {
|
|
967
1265
|
error.status = details.status;
|
|
968
1266
|
}
|
|
@@ -977,37 +1275,51 @@ class PhoneAuthClient {
|
|
|
977
1275
|
}
|
|
978
1276
|
return error;
|
|
979
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Type guard for AuthError
|
|
1280
|
+
*/
|
|
980
1281
|
isAuthError(error) {
|
|
981
1282
|
return error && typeof error.code === 'string' && typeof error.message === 'string';
|
|
982
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Debug logging
|
|
1286
|
+
*/
|
|
983
1287
|
log(...args) {
|
|
984
1288
|
if (this.debug) {
|
|
985
1289
|
console.log('[PhoneAuth]', ...args);
|
|
986
1290
|
}
|
|
987
1291
|
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Determine if an error should trigger a retry
|
|
1294
|
+
*/
|
|
988
1295
|
shouldRetry(error) {
|
|
989
1296
|
var _a, _b, _c;
|
|
1297
|
+
// Don't retry on 4xx client errors (these are not transient)
|
|
990
1298
|
if (error.status && error.status >= 400 && error.status < 500) {
|
|
991
1299
|
return false;
|
|
992
1300
|
}
|
|
1301
|
+
// Don't retry on explicit user denial or unsupported browser
|
|
1302
|
+
// USER_DENIED cannot be retried automatically because the Digital Credentials API
|
|
1303
|
+
// requires user interaction (transient activation)
|
|
993
1304
|
const nonRetryableCodes = [
|
|
994
1305
|
'USER_DENIED',
|
|
995
1306
|
'BROWSER_NOT_SUPPORTED',
|
|
996
1307
|
'INVALID_PHONE_NUMBER',
|
|
997
1308
|
'INVALID_PARAMETERS',
|
|
998
1309
|
'MISSING_PARAMETERS',
|
|
999
|
-
'UNPROCESSABLE_ENTITY',
|
|
1000
|
-
'USE_CASE_MISMATCH',
|
|
1001
|
-
'PHONE_NUMBER_MISMATCH',
|
|
1002
|
-
'VERIFICATION_FAILED',
|
|
1003
|
-
'INVALID_CREDENTIAL',
|
|
1004
|
-
'CARRIER_NOT_ELIGIBLE',
|
|
1005
|
-
'SESSION_EXPIRED',
|
|
1006
|
-
'INVALID_SESSION'
|
|
1310
|
+
'UNPROCESSABLE_ENTITY', // 422 errors
|
|
1311
|
+
'USE_CASE_MISMATCH', // Use case validation errors
|
|
1312
|
+
'PHONE_NUMBER_MISMATCH', // Phone verification failures
|
|
1313
|
+
'VERIFICATION_FAILED', // Verification failures
|
|
1314
|
+
'INVALID_CREDENTIAL', // Bad credentials
|
|
1315
|
+
'CARRIER_NOT_ELIGIBLE', // Carrier not supported
|
|
1316
|
+
'SESSION_EXPIRED', // Session expired
|
|
1317
|
+
'INVALID_SESSION' // Invalid session
|
|
1007
1318
|
];
|
|
1008
1319
|
if (nonRetryableCodes.includes(error.code)) {
|
|
1009
1320
|
return false;
|
|
1010
1321
|
}
|
|
1322
|
+
// Only retry on network and server errors (5xx)
|
|
1011
1323
|
const retryableCodes = [
|
|
1012
1324
|
'NETWORK_ERROR',
|
|
1013
1325
|
'REQUEST_TIMEOUT',
|
|
@@ -1016,6 +1328,7 @@ class PhoneAuthClient {
|
|
|
1016
1328
|
'BAD_GATEWAY',
|
|
1017
1329
|
'INTERNAL_SERVER_ERROR'
|
|
1018
1330
|
];
|
|
1331
|
+
// Also check for cross-device error types in details
|
|
1019
1332
|
const isCrossDeviceError = ((_a = error.details) === null || _a === void 0 ? void 0 : _a.errorType) && [
|
|
1020
1333
|
'CROSS_DEVICE_TIMEOUT',
|
|
1021
1334
|
'CROSS_DEVICE_CONNECTION_LOST',
|
|
@@ -1025,13 +1338,17 @@ class PhoneAuthClient {
|
|
|
1025
1338
|
isCrossDeviceError ||
|
|
1026
1339
|
(((_b = error.browserError) === null || _b === void 0 ? void 0 : _b.name) === 'NetworkError' && ((_c = error.browserError) === null || _c === void 0 ? void 0 : _c.code) !== 19);
|
|
1027
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Analyze and enhance errors specific to cross-device flows
|
|
1343
|
+
*/
|
|
1028
1344
|
analyzeCrossDeviceError(error, prepareResponse) {
|
|
1029
1345
|
var _a;
|
|
1030
1346
|
const errorObj = error;
|
|
1347
|
+
// Check for specific cross-device error patterns
|
|
1031
1348
|
const isCrossDeviceTimeout = (errorObj.name === 'AbortError' && this.crossDeviceActive) ||
|
|
1032
1349
|
(((_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.includes('timeout')) && this.crossDeviceActive);
|
|
1033
1350
|
const isCrossDeviceNetworkIssue = errorObj.name === 'NetworkError' &&
|
|
1034
|
-
errorObj.code !== 19 &&
|
|
1351
|
+
errorObj.code !== 19 && // Not user cancellation
|
|
1035
1352
|
this.crossDeviceActive;
|
|
1036
1353
|
if (isCrossDeviceTimeout) {
|
|
1037
1354
|
return {
|
|
@@ -1059,8 +1376,12 @@ class PhoneAuthClient {
|
|
|
1059
1376
|
browserError: error.browserError
|
|
1060
1377
|
};
|
|
1061
1378
|
}
|
|
1379
|
+
// Return the original error if not cross-device specific
|
|
1062
1380
|
return error;
|
|
1063
1381
|
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Cache successful session for retry scenarios
|
|
1384
|
+
*/
|
|
1064
1385
|
cacheSession(options, result) {
|
|
1065
1386
|
const cacheKey = this.getCacheKey(options);
|
|
1066
1387
|
this.sessionCache.set(cacheKey, {
|
|
@@ -1068,11 +1389,15 @@ class PhoneAuthClient {
|
|
|
1068
1389
|
data: result
|
|
1069
1390
|
});
|
|
1070
1391
|
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Retrieve cached session if available and recent
|
|
1394
|
+
*/
|
|
1071
1395
|
getCachedSession(options) {
|
|
1072
1396
|
const cacheKey = this.getCacheKey(options);
|
|
1073
1397
|
const cached = this.sessionCache.get(cacheKey);
|
|
1074
1398
|
if (!cached)
|
|
1075
1399
|
return null;
|
|
1400
|
+
// Cache valid for 5 minutes
|
|
1076
1401
|
const cacheValidMs = 5 * 60 * 1000;
|
|
1077
1402
|
if (Date.now() - cached.timestamp > cacheValidMs) {
|
|
1078
1403
|
this.sessionCache.delete(cacheKey);
|
|
@@ -1080,14 +1405,22 @@ class PhoneAuthClient {
|
|
|
1080
1405
|
}
|
|
1081
1406
|
return cached.data;
|
|
1082
1407
|
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Generate cache key for session storage
|
|
1410
|
+
*/
|
|
1083
1411
|
getCacheKey(options) {
|
|
1084
1412
|
var _a, _b;
|
|
1085
1413
|
return `${options.use_case}-${options.phone_number || 'no-phone'}-${((_a = options.plmn) === null || _a === void 0 ? void 0 : _a.mcc) || ''}-${((_b = options.plmn) === null || _b === void 0 ? void 0 : _b.mnc) || ''}`;
|
|
1086
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Set up periodic cache cleanup
|
|
1417
|
+
*/
|
|
1087
1418
|
setupCacheCleanup() {
|
|
1419
|
+
// Only run cleanup in browser environment (not during SSR)
|
|
1088
1420
|
if (typeof window === 'undefined') {
|
|
1089
1421
|
return;
|
|
1090
1422
|
}
|
|
1423
|
+
// Clean up expired cache entries every minute
|
|
1091
1424
|
setInterval(() => {
|
|
1092
1425
|
const now = Date.now();
|
|
1093
1426
|
const cacheValidMs = 5 * 60 * 1000;
|
|
@@ -1098,6 +1431,9 @@ class PhoneAuthClient {
|
|
|
1098
1431
|
}
|
|
1099
1432
|
}, 60000);
|
|
1100
1433
|
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Utility delay function
|
|
1436
|
+
*/
|
|
1101
1437
|
delay(ms) {
|
|
1102
1438
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1103
1439
|
}
|