@glideidentity/web-client-sdk 4.4.8-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.
Files changed (136) hide show
  1. package/README.md +938 -0
  2. package/dist/adapters/angular/client.service.d.ts +7 -0
  3. package/dist/adapters/angular/client.service.js +30 -0
  4. package/dist/adapters/angular/index.d.ts +3 -0
  5. package/dist/adapters/angular/index.js +18 -0
  6. package/dist/adapters/angular/phone-auth.service.d.ts +38 -0
  7. package/dist/adapters/angular/phone-auth.service.js +130 -0
  8. package/dist/adapters/react/index.d.ts +9 -0
  9. package/dist/adapters/react/index.js +28 -0
  10. package/dist/adapters/react/useClient.d.ts +26 -0
  11. package/dist/adapters/react/useClient.js +121 -0
  12. package/dist/adapters/react/usePhoneAuth.d.ts +23 -0
  13. package/dist/adapters/react/usePhoneAuth.js +95 -0
  14. package/dist/adapters/vanilla/client.d.ts +8 -0
  15. package/dist/adapters/vanilla/client.js +33 -0
  16. package/dist/adapters/vanilla/index.d.ts +3 -0
  17. package/dist/adapters/vanilla/index.js +18 -0
  18. package/dist/adapters/vanilla/phone-auth.d.ts +46 -0
  19. package/dist/adapters/vanilla/phone-auth.js +138 -0
  20. package/dist/adapters/vue/index.d.ts +10 -0
  21. package/dist/adapters/vue/index.js +36 -0
  22. package/dist/adapters/vue/useClient.d.ts +115 -0
  23. package/dist/adapters/vue/useClient.js +131 -0
  24. package/dist/adapters/vue/usePhoneAuth.d.ts +94 -0
  25. package/dist/adapters/vue/usePhoneAuth.js +103 -0
  26. package/dist/browser/web-client-sdk.min.js +2 -0
  27. package/dist/browser/web-client-sdk.min.js.LICENSE.txt +1 -0
  28. package/dist/browser.d.ts +7 -0
  29. package/dist/browser.js +31 -0
  30. package/dist/core/client.d.ts +22 -0
  31. package/dist/core/client.js +77 -0
  32. package/dist/core/logger.d.ts +130 -0
  33. package/dist/core/logger.js +370 -0
  34. package/dist/core/phone-auth/api-types.d.ts +525 -0
  35. package/dist/core/phone-auth/api-types.js +215 -0
  36. package/dist/core/phone-auth/client.d.ts +187 -0
  37. package/dist/core/phone-auth/client.js +1353 -0
  38. package/dist/core/phone-auth/error-utils.d.ts +110 -0
  39. package/dist/core/phone-auth/error-utils.js +350 -0
  40. package/dist/core/phone-auth/index.d.ts +7 -0
  41. package/dist/core/phone-auth/index.js +47 -0
  42. package/dist/core/phone-auth/status-types.d.ts +107 -0
  43. package/dist/core/phone-auth/status-types.js +31 -0
  44. package/dist/core/phone-auth/strategies/desktop.d.ts +113 -0
  45. package/dist/core/phone-auth/strategies/desktop.js +502 -0
  46. package/dist/core/phone-auth/strategies/index.d.ts +11 -0
  47. package/dist/core/phone-auth/strategies/index.js +15 -0
  48. package/dist/core/phone-auth/strategies/link.d.ts +81 -0
  49. package/dist/core/phone-auth/strategies/link.js +265 -0
  50. package/dist/core/phone-auth/strategies/ts43.d.ts +32 -0
  51. package/dist/core/phone-auth/strategies/ts43.js +146 -0
  52. package/dist/core/phone-auth/strategies/types.d.ts +18 -0
  53. package/dist/core/phone-auth/strategies/types.js +6 -0
  54. package/dist/core/phone-auth/type-guards.d.ts +125 -0
  55. package/dist/core/phone-auth/type-guards.js +160 -0
  56. package/dist/core/phone-auth/types.d.ts +232 -0
  57. package/dist/core/phone-auth/types.js +93 -0
  58. package/dist/core/phone-auth/ui/mobile-debug-console.d.ts +25 -0
  59. package/dist/core/phone-auth/ui/mobile-debug-console.js +288 -0
  60. package/dist/core/phone-auth/ui/modal.d.ts +84 -0
  61. package/dist/core/phone-auth/ui/modal.js +574 -0
  62. package/dist/core/phone-auth/validation-utils.d.ts +66 -0
  63. package/dist/core/phone-auth/validation-utils.js +182 -0
  64. package/dist/core/types.d.ts +62 -0
  65. package/dist/core/types.js +2 -0
  66. package/dist/core/version.d.ts +1 -0
  67. package/dist/core/version.js +5 -0
  68. package/dist/esm/adapters/angular/client.service.d.ts +7 -0
  69. package/dist/esm/adapters/angular/client.service.js +27 -0
  70. package/dist/esm/adapters/angular/index.d.ts +3 -0
  71. package/dist/esm/adapters/angular/index.js +4 -0
  72. package/dist/esm/adapters/angular/phone-auth.service.d.ts +38 -0
  73. package/dist/esm/adapters/angular/phone-auth.service.js +127 -0
  74. package/dist/esm/adapters/react/index.d.ts +9 -0
  75. package/dist/esm/adapters/react/index.js +8 -0
  76. package/dist/esm/adapters/react/useClient.d.ts +26 -0
  77. package/dist/esm/adapters/react/useClient.js +116 -0
  78. package/dist/esm/adapters/react/usePhoneAuth.d.ts +23 -0
  79. package/dist/esm/adapters/react/usePhoneAuth.js +92 -0
  80. package/dist/esm/adapters/vanilla/client.d.ts +8 -0
  81. package/dist/esm/adapters/vanilla/client.js +29 -0
  82. package/dist/esm/adapters/vanilla/index.d.ts +3 -0
  83. package/dist/esm/adapters/vanilla/index.js +4 -0
  84. package/dist/esm/adapters/vanilla/phone-auth.d.ts +46 -0
  85. package/dist/esm/adapters/vanilla/phone-auth.js +134 -0
  86. package/dist/esm/adapters/vue/index.d.ts +10 -0
  87. package/dist/esm/adapters/vue/index.js +11 -0
  88. package/dist/esm/adapters/vue/useClient.d.ts +115 -0
  89. package/dist/esm/adapters/vue/useClient.js +127 -0
  90. package/dist/esm/adapters/vue/usePhoneAuth.d.ts +94 -0
  91. package/dist/esm/adapters/vue/usePhoneAuth.js +100 -0
  92. package/dist/esm/browser.d.ts +7 -0
  93. package/dist/esm/browser.js +11 -0
  94. package/dist/esm/core/client.d.ts +22 -0
  95. package/dist/esm/core/client.js +70 -0
  96. package/dist/esm/core/logger.d.ts +130 -0
  97. package/dist/esm/core/logger.js +359 -0
  98. package/dist/esm/core/phone-auth/api-types.d.ts +525 -0
  99. package/dist/esm/core/phone-auth/api-types.js +203 -0
  100. package/dist/esm/core/phone-auth/client.d.ts +187 -0
  101. package/dist/esm/core/phone-auth/client.js +1316 -0
  102. package/dist/esm/core/phone-auth/error-utils.d.ts +110 -0
  103. package/dist/esm/core/phone-auth/error-utils.js +338 -0
  104. package/dist/esm/core/phone-auth/index.d.ts +7 -0
  105. package/dist/esm/core/phone-auth/index.js +6 -0
  106. package/dist/esm/core/phone-auth/status-types.d.ts +107 -0
  107. package/dist/esm/core/phone-auth/status-types.js +26 -0
  108. package/dist/esm/core/phone-auth/strategies/desktop.d.ts +113 -0
  109. package/dist/esm/core/phone-auth/strategies/desktop.js +496 -0
  110. package/dist/esm/core/phone-auth/strategies/index.d.ts +11 -0
  111. package/dist/esm/core/phone-auth/strategies/index.js +7 -0
  112. package/dist/esm/core/phone-auth/strategies/link.d.ts +81 -0
  113. package/dist/esm/core/phone-auth/strategies/link.js +261 -0
  114. package/dist/esm/core/phone-auth/strategies/ts43.d.ts +32 -0
  115. package/dist/esm/core/phone-auth/strategies/ts43.js +142 -0
  116. package/dist/esm/core/phone-auth/strategies/types.d.ts +18 -0
  117. package/dist/esm/core/phone-auth/strategies/types.js +5 -0
  118. package/dist/esm/core/phone-auth/type-guards.d.ts +125 -0
  119. package/dist/esm/core/phone-auth/type-guards.js +150 -0
  120. package/dist/esm/core/phone-auth/types.d.ts +232 -0
  121. package/dist/esm/core/phone-auth/types.js +76 -0
  122. package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +25 -0
  123. package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +284 -0
  124. package/dist/esm/core/phone-auth/ui/modal.d.ts +84 -0
  125. package/dist/esm/core/phone-auth/ui/modal.js +570 -0
  126. package/dist/esm/core/phone-auth/validation-utils.d.ts +66 -0
  127. package/dist/esm/core/phone-auth/validation-utils.js +174 -0
  128. package/dist/esm/core/types.d.ts +62 -0
  129. package/dist/esm/core/types.js +1 -0
  130. package/dist/esm/core/version.d.ts +1 -0
  131. package/dist/esm/core/version.js +2 -0
  132. package/dist/esm/index.d.ts +12 -0
  133. package/dist/esm/index.js +15 -0
  134. package/dist/index.d.ts +12 -0
  135. package/dist/index.js +52 -0
  136. package/package.json +92 -0
@@ -0,0 +1,1353 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.PhoneAuthClient = void 0;
46
+ // Import API types for API communication
47
+ const API = __importStar(require("./api-types"));
48
+ const types_1 = require("./types");
49
+ const error_utils_1 = require("./error-utils");
50
+ const validation_utils_1 = require("./validation-utils");
51
+ const logger_1 = require("../logger");
52
+ const desktop_1 = require("./strategies/desktop");
53
+ const link_1 = require("./strategies/link");
54
+ const modal_1 = require("./ui/modal");
55
+ class PhoneAuthClient {
56
+ constructor(config = {}) {
57
+ var _a, _b, _c;
58
+ this.crossDeviceActive = false;
59
+ this.retryCount = 0;
60
+ this.sessionCache = new Map();
61
+ // Store base timeout for normal operations
62
+ this.baseTimeout = config.timeout || 30000;
63
+ // Default configuration with cross-device support
64
+ this.config = {
65
+ endpoints: {
66
+ prepare: ((_a = config.endpoints) === null || _a === void 0 ? void 0 : _a.prepare) || '/api/magic-auth/prepare',
67
+ process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process'
68
+ },
69
+ timeout: config.timeout || 30000,
70
+ pollingInterval: config.pollingInterval || 2000, // Default 2 seconds
71
+ maxPollingAttempts: config.maxPollingAttempts || 30, // Changed from 150 to 30 (1 minute total)
72
+ debug: config.debug || false,
73
+ aggregatorId: config.aggregatorId || 'default',
74
+ devtools: config.devtools
75
+ };
76
+ this.debug = this.config.debug;
77
+ // Store callbacks
78
+ this.callbacks = {
79
+ onCrossDeviceDetected: config.onCrossDeviceDetected,
80
+ onRetryAttempt: config.onRetryAttempt
81
+ };
82
+ // Initialize logger based on config
83
+ this.logger = logger_1.LoggerFactory.create({
84
+ level: config.logLevel,
85
+ prefix: '[PhoneAuth]',
86
+ remote: config.remoteLogging,
87
+ custom: config.logger
88
+ });
89
+ // Initialize developer tools if configured
90
+ if (((_c = config.devtools) === null || _c === void 0 ? void 0 : _c.showMobileConsole) && typeof window !== 'undefined') {
91
+ Promise.resolve().then(() => __importStar(require('./ui/mobile-debug-console'))).then(({ MobileDebugConsole }) => {
92
+ MobileDebugConsole.init();
93
+ console.log('[PhoneAuth] Mobile debug console enabled');
94
+ }).catch(err => {
95
+ console.error('[PhoneAuth] Failed to load mobile debug console:', err);
96
+ });
97
+ }
98
+ // Set up session cache cleanup
99
+ this.setupCacheCleanup();
100
+ }
101
+ /**
102
+ * Get user-friendly error message using error utilities
103
+ */
104
+ getUserFriendlyMessage(error) {
105
+ if (typeof error === 'string') {
106
+ // For legacy string error codes
107
+ return (0, error_utils_1.getUserMessage)({ code: error });
108
+ }
109
+ return (0, error_utils_1.getUserMessage)(error);
110
+ }
111
+ /**
112
+ * Log error with proper context and sanitization
113
+ */
114
+ logError(error, context) {
115
+ // Create breadcrumb for error tracking
116
+ const breadcrumb = (0, error_utils_1.createErrorBreadcrumb)(error);
117
+ // Serialize error for logging (sanitized)
118
+ const serialized = (0, error_utils_1.serializeError)(error);
119
+ if (this.debug || !(0, error_utils_1.isUserError)(error)) {
120
+ console.error('[PhoneAuth] Error:', Object.assign(Object.assign({}, serialized), { breadcrumb,
121
+ context }));
122
+ }
123
+ // Log trace context for distributed tracing (if available)
124
+ if (error.traceId) {
125
+ console.debug('[PhoneAuth] Trace Context:', {
126
+ traceId: error.traceId,
127
+ spanId: error.spanId,
128
+ requestId: error.requestId
129
+ });
130
+ }
131
+ }
132
+ /**
133
+ * Check if the browser supports secure phone authentication
134
+ */
135
+ isSupported() {
136
+ // Only check on client side
137
+ if (typeof window === 'undefined')
138
+ return false;
139
+ // Check for the DigitalCredential constructor specifically
140
+ // This is more accurate than checking credentials.get which exists for other credential types
141
+ return 'DigitalCredential' in window;
142
+ }
143
+ /**
144
+ * Get detailed browser support information
145
+ */
146
+ getBrowserSupportInfo() {
147
+ if (typeof window === 'undefined') {
148
+ return {
149
+ supported: false,
150
+ browser: 'unknown',
151
+ message: 'Not running in a browser environment'
152
+ };
153
+ }
154
+ const userAgent = navigator.userAgent;
155
+ const isChrome = /Chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor);
156
+ const isEdge = /Edg\//.test(userAgent);
157
+ const isSupported = this.isSupported();
158
+ if (isSupported) {
159
+ return {
160
+ supported: true,
161
+ browser: isChrome ? types_1.BrowserName.CHROME : isEdge ? types_1.BrowserName.EDGE : types_1.BrowserName.OTHER
162
+ };
163
+ }
164
+ // Provide specific guidance based on browser
165
+ if (isChrome || isEdge) {
166
+ return {
167
+ supported: false,
168
+ browser: isChrome ? types_1.BrowserName.CHROME : types_1.BrowserName.EDGE,
169
+ message: 'Digital Credentials API is not enabled. Please enable the #web-identity-digital-credentials flag.',
170
+ helpUrl: isChrome
171
+ ? 'chrome://flags/#web-identity-digital-credentials'
172
+ : 'edge://flags/#web-identity-digital-credentials'
173
+ };
174
+ }
175
+ return {
176
+ supported: false,
177
+ browser: 'other',
178
+ message: 'Your browser doesn\'t support the Digital Credentials API. Please use Chrome or Edge with the #web-identity-digital-credentials flag enabled.'
179
+ };
180
+ }
181
+ /**
182
+ * Main verification method with silent retry support
183
+ */
184
+ verify(options) {
185
+ return __awaiter(this, void 0, void 0, function* () {
186
+ // Reset retry count for new verification
187
+ this.retryCount = 0;
188
+ this.lastRequest = options;
189
+ const maxRetries = 2; // Default max retries
190
+ // Try verification with silent retries
191
+ return this.verifyWithRetry(options, maxRetries);
192
+ });
193
+ }
194
+ verifyWithRetry(options, maxRetries) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ var _a, _b;
197
+ try {
198
+ // Step 1: Prepare the phone verification request
199
+ const preparedRequest = yield this.preparePhoneRequest(options);
200
+ // Step 2: Invoke secure prompt for user consent (always in UI mode for high-level API)
201
+ const credentialResponse = yield this.invokeSecurePrompt(preparedRequest);
202
+ // Check if headless result was returned (this shouldn't happen in high-level API)
203
+ if (credentialResponse && typeof credentialResponse === 'object' && 'strategy' in credentialResponse) {
204
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_RESPONSE, 'Headless mode is not supported in authenticatePhoneNumber. Use preparePhoneRequest and invokeSecurePrompt directly for headless mode.');
205
+ }
206
+ // Step 3: Process the response through appropriate endpoint
207
+ const credential = credentialResponse;
208
+ const result = options.use_case === API.USE_CASE.GET_PHONE_NUMBER
209
+ ? yield this.getPhoneNumber(credential, preparedRequest.session)
210
+ : yield this.verifyPhoneNumber(credential, preparedRequest.session);
211
+ // Return the result directly - it's already the correct type
212
+ // Cache successful result with session info for later use
213
+ this.cacheSession(options, result);
214
+ return result;
215
+ }
216
+ catch (error) {
217
+ const authError = (0, error_utils_1.isPhoneAuthError)(error) ? error : (0, error_utils_1.parseBackendError)(error);
218
+ // Check if we should retry (silent retry - don't throw yet)
219
+ // Note: We cannot automatically retry USER_DENIED errors because the Digital Credentials API
220
+ // requires user interaction (transient activation). Automatic retries would fail with
221
+ // "The 'digital-credentials-get' feature requires transient activation" error.
222
+ if (this.shouldRetry(authError) && this.retryCount < maxRetries) {
223
+ this.retryCount++;
224
+ // Notify about retry attempt (but don't show error to user)
225
+ (_b = (_a = this.callbacks).onRetryAttempt) === null || _b === void 0 ? void 0 : _b.call(_a, this.retryCount, maxRetries);
226
+ if (this.debug) {
227
+ console.log(`[PhoneAuth] Retrying verification (attempt ${this.retryCount + 1}/${maxRetries + 1})`);
228
+ }
229
+ // Wait before retry
230
+ yield this.delay(Math.min(1000 * Math.pow(2, this.retryCount - 1), 5000)); // Exponential backoff
231
+ // Check cache for recent successful session
232
+ const cachedResult = this.getCachedSession(options);
233
+ if (cachedResult) {
234
+ if (this.debug)
235
+ console.log('[PhoneAuth] Using cached session result');
236
+ return cachedResult;
237
+ }
238
+ // Retry the verification
239
+ return this.verifyWithRetry(options, maxRetries);
240
+ }
241
+ // All retries exhausted or non-retryable error - now throw
242
+ // Add context
243
+ authError.context = {
244
+ step: 'complete',
245
+ useCase: options.use_case,
246
+ timestamp: new Date().toISOString(),
247
+ userAgent: navigator.userAgent,
248
+ attemptNumber: this.retryCount + 1,
249
+ maxAttempts: maxRetries + 1
250
+ };
251
+ // Log error with proper sanitization
252
+ this.logError(authError, { options });
253
+ // Re-throw the structured error
254
+ if ((0, error_utils_1.isPhoneAuthError)(error)) {
255
+ // If it already has context, throw as-is
256
+ if (error.context) {
257
+ throw error;
258
+ }
259
+ // Otherwise, create a new error with context
260
+ const enhancedError = {
261
+ code: authError.code,
262
+ message: error.message,
263
+ details: error.details,
264
+ status: error.status,
265
+ requestId: error.requestId,
266
+ timestamp: error.timestamp,
267
+ retryAfter: error.retryAfter,
268
+ browserError: error.browserError,
269
+ context: {
270
+ useCase: options.use_case,
271
+ timestamp: new Date().toISOString(),
272
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
273
+ url: typeof window !== 'undefined' ? window.location.href : undefined,
274
+ attemptNumber: this.retryCount + 1,
275
+ maxAttempts: maxRetries + 1
276
+ }
277
+ };
278
+ throw enhancedError;
279
+ }
280
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.VERIFICATION_FAILED, 'Verification failed', error);
281
+ }
282
+ });
283
+ }
284
+ /**
285
+ * High-level method to get phone number (complete flow)
286
+ * Handles prepare, credential prompt, and get phone number in one call
287
+ */
288
+ getPhoneNumberComplete(options) {
289
+ return __awaiter(this, void 0, void 0, function* () {
290
+ return this.verify(Object.assign({ use_case: API.USE_CASE.GET_PHONE_NUMBER }, options));
291
+ });
292
+ }
293
+ /**
294
+ * High-level method to verify phone number (complete flow)
295
+ * Handles prepare, credential prompt, and verification in one call
296
+ */
297
+ verifyPhoneNumberComplete(phoneNumber, options) {
298
+ return __awaiter(this, void 0, void 0, function* () {
299
+ return this.verify(Object.assign({ use_case: API.USE_CASE.VERIFY_PHONE_NUMBER, phone_number: phoneNumber }, options));
300
+ });
301
+ }
302
+ /**
303
+ * Step 1: Prepare phone verification request
304
+ *
305
+ * This method prepares a secure request for phone verification.
306
+ * You can use this with your own backend or the glide-sdk-node.
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const request = await phoneAuthClient.preparePhoneRequest({ useCase: 'GetPhoneNumber' });
311
+ * // Handle the request with custom logic
312
+ * ```
313
+ */
314
+ preparePhoneRequest(options) {
315
+ return __awaiter(this, void 0, void 0, function* () {
316
+ // Validate use case requirements first
317
+ // const useCaseValidation = validateUseCaseRequirements(options.use_case, options.phone_number);
318
+ // if (!useCaseValidation.valid) {
319
+ // throw this.createError(
320
+ // PhoneAuthErrorCode.BAD_REQUEST,
321
+ // useCaseValidation.error!,
322
+ // { field: 'use_case' }
323
+ // );
324
+ // }
325
+ var _a, _b;
326
+ // Validate phone number if provided
327
+ if (options.phone_number) {
328
+ const phoneValidation = (0, validation_utils_1.validatePhoneNumber)(options.phone_number);
329
+ if (!phoneValidation.valid) {
330
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_PHONE_NUMBER, phoneValidation.error, { field: 'phone_number' });
331
+ }
332
+ }
333
+ // Validate PLMN if provided
334
+ if (options.plmn) {
335
+ const plmnValidation = (0, validation_utils_1.validatePlmn)(options.plmn);
336
+ if (!plmnValidation.valid) {
337
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.BAD_REQUEST, plmnValidation.error, { field: 'plmn' });
338
+ }
339
+ }
340
+ // Validate consent data if provided
341
+ if (options.consent_data) {
342
+ const consentValidation = (0, validation_utils_1.validateConsentData)(options.consent_data);
343
+ if (!consentValidation.valid) {
344
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.BAD_REQUEST, consentValidation.error, { field: 'consent_data' });
345
+ }
346
+ }
347
+ // Validate required parameters based on use case
348
+ if (!options.phone_number && !options.plmn && !((_a = options.options) === null || _a === void 0 ? void 0 : _a.parent_session_id)) {
349
+ // Provide specific error message based on use case
350
+ if (options.use_case === API.USE_CASE.GET_PHONE_NUMBER) {
351
+ 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' });
352
+ }
353
+ else if (options.use_case === API.USE_CASE.VERIFY_PHONE_NUMBER) {
354
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Phone number is required for VerifyPhoneNumber', { field: 'phoneNumber', useCase: 'VerifyPhoneNumber' });
355
+ }
356
+ else if (!options.use_case) {
357
+ // If no use case, that's an error
358
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
359
+ }
360
+ else {
361
+ // Fallback for other use cases
362
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Either phone number or PLMN (MCC/MNC) must be provided', { field: 'phoneNumber,plmn' });
363
+ }
364
+ }
365
+ // If only parent_session_id is provided, use_case is optional (will be inherited)
366
+ if (((_b = options.options) === null || _b === void 0 ? void 0 : _b.parent_session_id) && !options.phone_number && !options.plmn && !options.use_case) {
367
+ if (this.debug) {
368
+ console.log('[PhoneAuth] Using parent_session_id only - all fields will be inherited from parent session');
369
+ }
370
+ }
371
+ const requestId = `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
372
+ // Generate cryptographically secure nonce
373
+ const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))))
374
+ .replace(/\+/g, '-')
375
+ .replace(/\//g, '_')
376
+ .replace(/=/g, '');
377
+ // Validate generated nonce
378
+ const nonceValidation = (0, validation_utils_1.validateNonce)(nonce);
379
+ if (!nonceValidation.valid) {
380
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, 'Failed to generate valid nonce', { field: 'nonce' });
381
+ }
382
+ // Build properly typed request body according to API specification
383
+ const requestBody = {
384
+ // Required fields
385
+ use_case: options.use_case,
386
+ // One of these is required
387
+ phone_number: options.phone_number,
388
+ plmn: options.plmn ? {
389
+ mcc: options.plmn.mcc,
390
+ mnc: options.plmn.mnc
391
+ } : undefined,
392
+ // Auto-generated fields (SDK always provides these)
393
+ nonce: nonce,
394
+ id: requestId,
395
+ // Optional fields
396
+ consent_data: options.consent_data,
397
+ client_info: {
398
+ user_agent: navigator.userAgent,
399
+ platform: navigator.platform
400
+ },
401
+ // Advanced options (for desktop-mobile binding and future features)
402
+ options: options.options
403
+ };
404
+ this.log('Preparing phone verification request', requestBody);
405
+ try {
406
+ const response = yield this.fetchWithTimeout(this.config.endpoints.prepare, {
407
+ method: 'POST',
408
+ headers: { 'Content-Type': 'application/json' },
409
+ body: JSON.stringify(requestBody)
410
+ });
411
+ if (!response.ok) {
412
+ // Try to get error details from response body
413
+ let errorDetails = null;
414
+ try {
415
+ errorDetails = yield response.json();
416
+ // Always include the HTTP status from the response
417
+ errorDetails = Object.assign(Object.assign({}, errorDetails), { status: response.status });
418
+ }
419
+ catch (_c) {
420
+ // If JSON parsing fails, use status text
421
+ errorDetails = { status: response.status, statusText: response.statusText };
422
+ }
423
+ // Parse the backend error response (handles both structured and unstructured errors)
424
+ const parsedError = (0, error_utils_1.parseBackendError)(errorDetails);
425
+ // Enhance with additional context
426
+ parsedError.context = {
427
+ step: 'prepare',
428
+ useCase: options.use_case,
429
+ timestamp: new Date().toISOString(),
430
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
431
+ url: typeof window !== 'undefined' ? window.location.href : undefined
432
+ };
433
+ // Add endpoint info
434
+ parsedError.details = Object.assign(Object.assign({}, parsedError.details), { endpoint: 'prepare', status: response.status });
435
+ throw parsedError;
436
+ }
437
+ const data = yield response.json();
438
+ this.log('Phone verification request prepared', data);
439
+ if (!data.authentication_strategy || !data.data || !data.session) {
440
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_RESPONSE, 'Invalid response format from backend');
441
+ }
442
+ // Return the full response as-is
443
+ // The invoke method will handle it based on authentication_strategy
444
+ return data;
445
+ }
446
+ catch (error) {
447
+ // If it's already an AuthError, re-throw it
448
+ if (this.isAuthError(error)) {
449
+ throw error;
450
+ }
451
+ // Otherwise, wrap it as a network error
452
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Failed to prepare verification request', {
453
+ originalError: error,
454
+ context: {
455
+ step: 'prepare',
456
+ useCase: options.use_case,
457
+ timestamp: new Date().toISOString(),
458
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
459
+ url: typeof window !== 'undefined' ? window.location.href : undefined
460
+ }
461
+ });
462
+ }
463
+ });
464
+ }
465
+ /**
466
+ * Step 2: Invoke secure prompt for user consent
467
+ *
468
+ * This method can work in two modes:
469
+ * 1. **UI Mode (default)**: Shows built-in UI components (modals/buttons)
470
+ * 2. **Headless Mode**: Returns raw data for custom UI implementation
471
+ *
472
+ * **Important**: This method automatically handles reactive objects from frameworks
473
+ * like Vue.js and React by deep cloning the input. This ensures compatibility with
474
+ * browser APIs that expect plain objects.
475
+ *
476
+ * @example UI Mode (shows modal/button)
477
+ * ```typescript
478
+ * // Shows SDK's built-in UI
479
+ * const credential = await phoneAuth.invokeSecurePrompt(prepareResult);
480
+ *
481
+ * // Customize the UI
482
+ * const credential = await phoneAuth.invokeSecurePrompt(prepareResult, {
483
+ * modalOptions: {
484
+ * title: 'Verify Your Identity',
485
+ * buttonText: 'Continue with Verizon'
486
+ * }
487
+ * });
488
+ * ```
489
+ *
490
+ * @example Headless Mode (returns data)
491
+ * ```typescript
492
+ * // Get raw data without showing UI
493
+ * const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
494
+ * headless: true
495
+ * });
496
+ *
497
+ * if (result.strategy === 'link') {
498
+ * // Open URL yourself
499
+ * window.open(result.url);
500
+ * await result.pollingPromise; // Wait for completion
501
+ * }
502
+ * ```
503
+ *
504
+ * @param prepareResponse - Response from prepare() with strategy and data
505
+ * @param options - Control UI behavior or enable headless mode
506
+ * @returns Credential or HeadlessResult based on mode
507
+ */
508
+ invokeSecurePrompt(prepareResponse, options) {
509
+ return __awaiter(this, void 0, void 0, function* () {
510
+ // Deep clone to avoid issues with reactive objects (Vue/React)
511
+ // This ensures we work with plain objects for browser APIs
512
+ // Vue's reactivity system wraps objects in Proxies which can interfere
513
+ // with browser APIs like Digital Credentials API that expect plain objects
514
+ var _a, _b, _c;
515
+ // Try structuredClone first (modern browsers), but catch errors and fallback to JSON method
516
+ let plainResponse;
517
+ try {
518
+ // structuredClone might throw if object contains non-cloneable properties
519
+ plainResponse = typeof structuredClone !== 'undefined'
520
+ ? structuredClone(prepareResponse)
521
+ : JSON.parse(JSON.stringify(prepareResponse));
522
+ }
523
+ catch (cloneError) {
524
+ // Fallback to JSON method if structuredClone fails
525
+ if (this.debug) {
526
+ console.log('[PhoneAuth] structuredClone failed, using JSON fallback:', cloneError);
527
+ }
528
+ plainResponse = JSON.parse(JSON.stringify(prepareResponse));
529
+ }
530
+ console.log('[PhoneAuth] === invokeSecurePrompt called ===');
531
+ console.log('[PhoneAuth] Session cache size:', this.sessionCache.size);
532
+ console.log('[PhoneAuth] Retry count:', this.retryCount);
533
+ console.log('[PhoneAuth] PrepareResponse received:', JSON.stringify(plainResponse, null, 2));
534
+ // Check if we're in headless mode (new InvokeOptions format)
535
+ // Properly detect InvokeOptions by checking for any of its properties
536
+ const invokeOptions = options && ('headless' in options ||
537
+ 'theme' in options ||
538
+ 'modalOptions' in options ||
539
+ 'callbacks' in options) ? options : undefined;
540
+ // Smart defaults: Link and TS43 default to headless, Desktop defaults to UI mode
541
+ const strategy = plainResponse.authentication_strategy;
542
+ const defaultHeadless = strategy === 'link' || strategy === 'ts43' ? true : false;
543
+ const isHeadless = (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.headless) !== null && _a !== void 0 ? _a : defaultHeadless;
544
+ // DesktopAuthOptions is only used if not InvokeOptions
545
+ const desktopOptions = options && !invokeOptions ? options : undefined;
546
+ // Handle based on authentication strategy
547
+ if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.TS43) {
548
+ // Check browser support for TS43 strategy which requires Digital Credentials API
549
+ if (!this.isSupported()) {
550
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.BROWSER_NOT_SUPPORTED, 'Your browser does not support the Digital Credentials API required for TS43 authentication');
551
+ }
552
+ const ts43Data = plainResponse.data;
553
+ const secureCredentialRequest = {
554
+ digital: {
555
+ requests: [{
556
+ protocol: ts43Data.protocol,
557
+ data: ts43Data.data
558
+ }]
559
+ }
560
+ };
561
+ this.log('Invoking TS43 secure authentication prompt', secureCredentialRequest);
562
+ // Function to trigger TS43 authentication
563
+ const triggerTS43 = () => __awaiter(this, void 0, void 0, function* () {
564
+ var _a, _b;
565
+ try {
566
+ // This is the browser API call for TS43
567
+ // Cast to CredentialRequestOptions with digital field (TS43 specific)
568
+ const credentialOptions = secureCredentialRequest;
569
+ const credentialResponse = yield navigator.credentials.get(credentialOptions);
570
+ // Type guard for Digital Credential response
571
+ const digitalResponse = credentialResponse;
572
+ if (!digitalResponse || !('data' in digitalResponse) || !digitalResponse.data) {
573
+ // Check if this is likely due to the browser flag being disabled
574
+ const supportInfo = this.getBrowserSupportInfo();
575
+ if (supportInfo.browser === types_1.BrowserName.CHROME || supportInfo.browser === types_1.BrowserName.EDGE) {
576
+ 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.`);
577
+ }
578
+ throw new Error('Digital Credentials API returned no response. Your browser may not fully support this feature.');
579
+ }
580
+ const credentialData = digitalResponse.data;
581
+ this.log('Secure credential response received', credentialData);
582
+ return credentialData.vp_token;
583
+ }
584
+ catch (error) {
585
+ // Capture detailed browser error information
586
+ const errorObj = error;
587
+ const browserErrorDetails = {
588
+ name: errorObj.name || 'UnknownError',
589
+ message: errorObj.message || 'Unknown error occurred',
590
+ stack: errorObj.stack,
591
+ code: errorObj.code
592
+ };
593
+ const errorContext = {
594
+ step: 'prompt',
595
+ timestamp: new Date().toISOString(),
596
+ userAgent: navigator.userAgent,
597
+ url: window.location.href,
598
+ // Include request details for debugging
599
+ authentication_strategy: plainResponse.authentication_strategy,
600
+ hasSession: !!plainResponse.session
601
+ };
602
+ // Handle specific browser errors
603
+ if (errorObj.name === types_1.BrowserError.NOT_ALLOWED) {
604
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'User denied the credential request or the request timed out', {
605
+ originalError: error,
606
+ browserError: browserErrorDetails,
607
+ context: errorContext
608
+ });
609
+ }
610
+ // NetworkError with code 19 specifically indicates user cancellation in Digital Credentials API
611
+ if (errorObj.name === types_1.BrowserError.NETWORK && errorObj.code === types_1.BrowserErrorCode.USER_CANCELLED_DC_API) {
612
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled by user', {
613
+ originalError: error,
614
+ browserError: browserErrorDetails,
615
+ context: errorContext
616
+ });
617
+ }
618
+ // NetworkError without code 19 is a real network error
619
+ if (errorObj.name === types_1.BrowserError.NETWORK) {
620
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Network error occurred while retrieving credentials', {
621
+ originalError: error,
622
+ browserError: browserErrorDetails,
623
+ context: errorContext
624
+ });
625
+ }
626
+ if (errorObj.name === types_1.BrowserError.NOT_SUPPORTED) {
627
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.BROWSER_NOT_SUPPORTED, 'Your browser does not support the Digital Credentials API', {
628
+ originalError: error,
629
+ browserError: browserErrorDetails,
630
+ context: errorContext
631
+ });
632
+ }
633
+ if (errorObj.name === types_1.BrowserError.SECURITY) {
634
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.FORBIDDEN, 'Security error: This feature requires a secure context (HTTPS)', {
635
+ originalError: error,
636
+ browserError: browserErrorDetails,
637
+ context: errorContext
638
+ });
639
+ }
640
+ // Check for other cancellation patterns
641
+ if (errorObj.name === types_1.BrowserError.ABORT ||
642
+ ((_a = browserErrorDetails.message) === null || _a === void 0 ? void 0 : _a.includes('The operation was aborted')) ||
643
+ ((_b = browserErrorDetails.message) === null || _b === void 0 ? void 0 : _b.includes('User cancelled'))) {
644
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled', {
645
+ originalError: error,
646
+ browserError: browserErrorDetails,
647
+ context: errorContext
648
+ });
649
+ }
650
+ // For any other errors, capture all details
651
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Digital Credentials API error: ${errorObj.message || 'Unknown error'}`, {
652
+ originalError: error,
653
+ browserError: browserErrorDetails,
654
+ context: errorContext
655
+ });
656
+ }
657
+ });
658
+ // IMPORTANT: For TS43, we ALWAYS call the API directly without any modal
659
+ // The Digital Credentials API provides its own OS-level UI (drawer/bottom sheet)
660
+ // Adding our own modal would be redundant and confusing for users
661
+ // Unlike Link (which needs a button for iOS App Clips), TS43 just needs direct invocation
662
+ // Enhanced trigger function with callback support
663
+ const enhancedTriggerTS43 = () => __awaiter(this, void 0, void 0, function* () {
664
+ var _a, _b;
665
+ try {
666
+ const result = yield triggerTS43();
667
+ (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
668
+ strategy: API.AUTHENTICATION_STRATEGY.TS43,
669
+ success: true
670
+ });
671
+ return result;
672
+ }
673
+ catch (error) {
674
+ (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
675
+ strategy: API.AUTHENTICATION_STRATEGY.TS43,
676
+ success: false,
677
+ error
678
+ });
679
+ throw error;
680
+ }
681
+ });
682
+ // Headless mode for TS43 still returns the trigger function for custom UI
683
+ // This allows developers to control WHEN to trigger, but not HOW
684
+ if (isHeadless) {
685
+ // Auto-trigger for TS43 if enabled (may fail due to browser restrictions)
686
+ if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
687
+ try {
688
+ // Try to trigger immediately if we have user gesture context
689
+ const result = yield enhancedTriggerTS43();
690
+ // If successful, return the result directly
691
+ return result;
692
+ }
693
+ catch (error) {
694
+ // If auto-trigger fails, continue to return HeadlessResult with trigger
695
+ if (this.debug) {
696
+ console.log('[TS43] Auto-trigger failed, returning trigger function:', error);
697
+ }
698
+ }
699
+ }
700
+ // Return trigger function for developers who want to control timing
701
+ return {
702
+ strategy: API.AUTHENTICATION_STRATEGY.TS43,
703
+ credentialRequest: secureCredentialRequest,
704
+ trigger: enhancedTriggerTS43,
705
+ session: plainResponse.session
706
+ };
707
+ }
708
+ else {
709
+ // In UI mode, directly invoke the Digital Credentials API
710
+ // No modal needed - the OS provides its own UI
711
+ return yield enhancedTriggerTS43();
712
+ }
713
+ }
714
+ else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.DESKTOP) {
715
+ // Desktop strategy - QR code based authentication
716
+ const desktopData = plainResponse.data;
717
+ // Check if headless mode is requested
718
+ if (isHeadless) {
719
+ // Return raw data for custom UI implementation
720
+ const handler = new desktop_1.DesktopHandler();
721
+ // Start polling in the background
722
+ const pollingPromise = handler.invoke(plainResponse, Object.assign(Object.assign({}, desktopOptions), {
723
+ // Don't pass config.endpoints.polling - let backend-provided status_url take priority
724
+ pollingInterval: this.config.pollingInterval || 2000, maxPollingAttempts: this.config.maxPollingAttempts || 150,
725
+ // No UI callbacks in headless mode
726
+ onQRCodeReady: undefined, onStatusUpdate: undefined })).then(result => {
727
+ if (result.authenticated && result.credential) {
728
+ // Extract session ID from nested or flat structure
729
+ let sessionId = 'default';
730
+ // Check if desktopData has nested structure
731
+ if (desktopData && typeof desktopData === 'object') {
732
+ if (desktopData.data && typeof desktopData.data === 'object') {
733
+ // Nested structure: data.data.session_id
734
+ sessionId = desktopData.data.session_id || sessionId;
735
+ }
736
+ // Also check flat structure
737
+ if (!sessionId || sessionId === 'default') {
738
+ sessionId = desktopData.session_id || sessionId;
739
+ }
740
+ }
741
+ // Return credential in expected format for subsequent API calls
742
+ return {
743
+ [sessionId]: result.credential
744
+ };
745
+ }
746
+ else {
747
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
748
+ }
749
+ });
750
+ return {
751
+ strategy: API.AUTHENTICATION_STRATEGY.DESKTOP,
752
+ qrCode: (_b = desktopData.data) === null || _b === void 0 ? void 0 : _b.qr_code_image, // data.data.qr_code_image
753
+ pollingPromise,
754
+ session: plainResponse.session
755
+ };
756
+ }
757
+ else {
758
+ // UI mode - show QR code modal
759
+ const handler = new desktop_1.DesktopHandler();
760
+ const modal = new modal_1.AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
761
+ // Default options with modal display
762
+ const options = Object.assign(Object.assign({}, desktopOptions), {
763
+ // Don't pass config.endpoints.polling - let backend-provided status_url take priority
764
+ // Only explicit desktopOptions.pollingEndpoint will override backend URL
765
+ pollingInterval: (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 150, onQRCodeReady: (qrCodeData) => {
766
+ // Show QR code in modal (supports both single and dual-platform)
767
+ modal.showQRCode(qrCodeData, 'Scan with your mobile device');
768
+ }, onStatusUpdate: (status) => {
769
+ // Update status in modal
770
+ if (status.status === 'pending') {
771
+ modal.updateStatus('Waiting for authentication...');
772
+ }
773
+ else if (status.status === 'authenticated') {
774
+ modal.updateStatus('Authentication successful!');
775
+ setTimeout(() => modal.close(), 1500);
776
+ }
777
+ else if (status.status === 'expired') {
778
+ modal.updateStatus('QR code expired', true);
779
+ }
780
+ else if (status.status === 'error') {
781
+ modal.updateStatus('Authentication failed', true);
782
+ }
783
+ } });
784
+ try {
785
+ const result = yield handler.invoke(plainResponse, options);
786
+ if (result.authenticated && result.credential) {
787
+ // Extract session ID from nested or flat structure
788
+ let sessionId = 'default';
789
+ // Check if desktopData has nested structure
790
+ if (desktopData && typeof desktopData === 'object') {
791
+ if (desktopData.data && typeof desktopData.data === 'object') {
792
+ // Nested structure: data.data.session_id
793
+ sessionId = desktopData.data.session_id || sessionId;
794
+ }
795
+ // Also check flat structure
796
+ if (!sessionId || sessionId === 'default') {
797
+ sessionId = desktopData.session_id || sessionId;
798
+ }
799
+ }
800
+ // Return credential in expected format for subsequent API calls
801
+ return {
802
+ [sessionId]: result.credential
803
+ };
804
+ }
805
+ else {
806
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
807
+ }
808
+ }
809
+ catch (error) {
810
+ if (error instanceof Error && error.name === 'PhoneAuthError') {
811
+ throw error;
812
+ }
813
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Desktop authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
814
+ }
815
+ finally {
816
+ // Ensure handler cleanup
817
+ handler.cleanup();
818
+ modal.close();
819
+ }
820
+ }
821
+ }
822
+ else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.LINK) {
823
+ // Link strategy - app-based authentication (iOS/Android)
824
+ const linkData = plainResponse.data;
825
+ // Check if headless mode is requested
826
+ if (isHeadless) {
827
+ // Return raw data for custom UI implementation
828
+ const handler = new link_1.LinkHandler();
829
+ // Create reusable trigger function that ONLY opens the App Clip
830
+ // This can be called multiple times without restarting polling
831
+ const triggerLink = () => {
832
+ var _a, _b;
833
+ try {
834
+ window.open(linkData.url, '_blank');
835
+ (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
836
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
837
+ url: linkData.url,
838
+ success: true
839
+ });
840
+ }
841
+ catch (error) {
842
+ (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
843
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
844
+ url: linkData.url,
845
+ success: false,
846
+ error
847
+ });
848
+ }
849
+ };
850
+ // Auto-trigger by default for Link strategy
851
+ // This opens the App Clip immediately while preserving user gesture context
852
+ if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
853
+ triggerLink();
854
+ }
855
+ // Start polling in the background (independent of trigger)
856
+ const pollingPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
857
+ var _a;
858
+ try {
859
+ const result = yield handler.invoke(plainResponse, {
860
+ pollingEndpoint: (_a = this.config.endpoints) === null || _a === void 0 ? void 0 : _a.polling,
861
+ pollingInterval: this.config.pollingInterval || 2000,
862
+ maxPollingAttempts: this.config.maxPollingAttempts || 150,
863
+ // openMethod removed - always uses window.open()
864
+ onLinkOpened: undefined,
865
+ onStatusUpdate: undefined
866
+ });
867
+ if (result.authenticated && result.credential) {
868
+ // Return credential in the expected format
869
+ const aggregatorId = this.config.aggregatorId || 'default';
870
+ resolve({ [aggregatorId]: result.credential });
871
+ }
872
+ else {
873
+ reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed'));
874
+ }
875
+ }
876
+ catch (error) {
877
+ reject(error);
878
+ }
879
+ }));
880
+ return {
881
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
882
+ url: linkData.url,
883
+ pollingPromise,
884
+ session: plainResponse.session,
885
+ // Trigger function to open App Clip (can be called multiple times)
886
+ // Does NOT restart polling - just opens the URL
887
+ trigger: triggerLink
888
+ };
889
+ }
890
+ else {
891
+ // UI mode - show button to open app link
892
+ const modal = new modal_1.AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
893
+ const handler = new link_1.LinkHandler();
894
+ // Show link button in modal - user MUST click the button to open the app link
895
+ // This is critical for iOS to recognize the app link (requires user interaction)
896
+ yield modal.showLinkButton(linkData.url);
897
+ // Link options - the app link will be opened by user clicking the modal button
898
+ const options = {
899
+ // Use configured polling endpoint if available
900
+ pollingEndpoint: (_c = this.config.endpoints) === null || _c === void 0 ? void 0 : _c.polling,
901
+ pollingInterval: this.config.pollingInterval || 2000, // Default 2 second interval
902
+ maxPollingAttempts: this.config.maxPollingAttempts || 150, // Default 5 minutes total
903
+ onLinkOpened: () => {
904
+ this.log('Authentication app link opened');
905
+ modal.updateStatus('Waiting for app authentication...');
906
+ },
907
+ onStatusUpdate: (status) => {
908
+ this.log('Link authentication status update:', status);
909
+ if (status.status === 'authenticated') {
910
+ modal.updateStatus('Authentication successful!');
911
+ setTimeout(() => modal.close(), 1500);
912
+ }
913
+ else if (status.status === 'error') {
914
+ modal.updateStatus('Authentication failed', true);
915
+ }
916
+ },
917
+ onTimeout: () => {
918
+ this.log('Link authentication timed out');
919
+ modal.updateStatus('Authentication timed out', true);
920
+ }
921
+ };
922
+ try {
923
+ const result = yield handler.invoke(plainResponse, options);
924
+ if (result.authenticated && result.credential) {
925
+ // Return the credential for further processing
926
+ // The credential is typically the session key
927
+ const aggregatorId = this.config.aggregatorId || 'default';
928
+ modal.close();
929
+ return { [aggregatorId]: result.credential };
930
+ }
931
+ else {
932
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.VERIFICATION_FAILED, result.error || 'Link authentication failed');
933
+ }
934
+ }
935
+ catch (error) {
936
+ modal.close();
937
+ if (error instanceof Error && error.name === 'PhoneAuthError') {
938
+ throw error;
939
+ }
940
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Link authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
941
+ }
942
+ finally {
943
+ // Ensure handler cleanup
944
+ handler.cleanup();
945
+ }
946
+ }
947
+ }
948
+ else {
949
+ // Unknown strategy
950
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.UNSUPPORTED_STRATEGY, `Unknown authentication strategy: ${plainResponse.authentication_strategy}`);
951
+ }
952
+ });
953
+ }
954
+ /**
955
+ * Step 3A: Get phone number from credential
956
+ *
957
+ * @example
958
+ * ```typescript
959
+ * const prepareResp = await phoneAuthClient.preparePhoneRequest({ useCase: 'GetPhoneNumber', plmn: {...} });
960
+ * const credential = await phoneAuthClient.invokeSecurePrompt(prepareResp);
961
+ * const result = await phoneAuthClient.getPhoneNumber(credential, prepareResp.session);
962
+ * console.log(result.phone_number); // +1234567890
963
+ * ```
964
+ */
965
+ getPhoneNumber(credentialResponse, session) {
966
+ return __awaiter(this, void 0, void 0, function* () {
967
+ // Extract credential string
968
+ const credentialString = this.extractCredentialString(credentialResponse);
969
+ // Build request body for GetPhoneNumber
970
+ const requestBody = {
971
+ session: session,
972
+ credential: credentialString,
973
+ use_case: API.USE_CASE.GET_PHONE_NUMBER // Required for server routing
974
+ };
975
+ // Only show full details in debug mode, mask sensitive data otherwise
976
+ if (this.config.debug) {
977
+ this.log('Getting phone number from credential', {
978
+ session: session,
979
+ credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined', // Show partial for debugging
980
+ endpoint: this.config.endpoints.process || '/api/phone-auth/process'
981
+ });
982
+ }
983
+ else {
984
+ this.log('Getting phone number from credential');
985
+ }
986
+ try {
987
+ const response = yield this.fetchWithTimeout(this.config.endpoints.process || '/api/phone-auth/process', {
988
+ method: 'POST',
989
+ headers: { 'Content-Type': 'application/json' },
990
+ body: JSON.stringify(requestBody)
991
+ });
992
+ if (!response.ok) {
993
+ const errorDetails = yield this.extractErrorDetails(response);
994
+ const parsedError = (0, error_utils_1.parseBackendError)(errorDetails);
995
+ parsedError.context = {
996
+ step: 'process',
997
+ timestamp: new Date().toISOString(),
998
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
999
+ url: typeof window !== 'undefined' ? window.location.href : undefined
1000
+ };
1001
+ throw parsedError;
1002
+ }
1003
+ const data = yield response.json();
1004
+ this.log('Phone number retrieved', { phone_number: data.phone_number });
1005
+ if (!data.phone_number) {
1006
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.VERIFICATION_FAILED, 'No phone number returned from server');
1007
+ }
1008
+ return data;
1009
+ }
1010
+ catch (error) {
1011
+ if (this.isAuthError(error))
1012
+ throw error;
1013
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Failed to get phone number', {
1014
+ originalError: error,
1015
+ context: {
1016
+ step: 'process',
1017
+ timestamp: new Date().toISOString()
1018
+ }
1019
+ });
1020
+ }
1021
+ });
1022
+ }
1023
+ /**
1024
+ * Step 3B: Verify phone number with credential
1025
+ *
1026
+ * @example
1027
+ * ```typescript
1028
+ * const prepareResp = await phoneAuthClient.preparePhoneRequest({
1029
+ * useCase: 'VerifyPhoneNumber',
1030
+ * phoneNumber: '+1234567890'
1031
+ * });
1032
+ * const credential = await phoneAuthClient.invokeSecurePrompt(prepareResp);
1033
+ * const result = await phoneAuthClient.verifyPhoneNumber(credential, prepareResp.session);
1034
+ * console.log(result.verified); // true
1035
+ * ```
1036
+ */
1037
+ verifyPhoneNumber(credentialResponse, session) {
1038
+ return __awaiter(this, void 0, void 0, function* () {
1039
+ // Extract credential string
1040
+ const credentialString = this.extractCredentialString(credentialResponse);
1041
+ // Build request body for VerifyPhoneNumber
1042
+ const requestBody = {
1043
+ session: session,
1044
+ credential: credentialString,
1045
+ use_case: API.USE_CASE.VERIFY_PHONE_NUMBER // Required for server routing
1046
+ };
1047
+ // Only show full details in debug mode, mask sensitive data otherwise
1048
+ if (this.config.debug) {
1049
+ this.log('Verifying phone number with credential', {
1050
+ session: session,
1051
+ credential: credentialString ? credentialString.substring(0, 50) + '...' : 'undefined', // Show partial for debugging
1052
+ endpoint: this.config.endpoints.process || '/api/phone-auth/process'
1053
+ });
1054
+ }
1055
+ else {
1056
+ this.log('Verifying phone number');
1057
+ }
1058
+ try {
1059
+ const response = yield this.fetchWithTimeout(this.config.endpoints.process || '/api/phone-auth/process', {
1060
+ method: 'POST',
1061
+ headers: { 'Content-Type': 'application/json' },
1062
+ body: JSON.stringify(requestBody)
1063
+ });
1064
+ if (!response.ok) {
1065
+ const errorDetails = yield this.extractErrorDetails(response);
1066
+ const parsedError = (0, error_utils_1.parseBackendError)(errorDetails);
1067
+ parsedError.context = {
1068
+ step: 'process',
1069
+ timestamp: new Date().toISOString(),
1070
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
1071
+ url: typeof window !== 'undefined' ? window.location.href : undefined
1072
+ };
1073
+ throw parsedError;
1074
+ }
1075
+ const data = yield response.json();
1076
+ this.log('Phone number verification result', {
1077
+ phone_number: data.phone_number,
1078
+ verified: data.verified
1079
+ });
1080
+ if (!data.phone_number) {
1081
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.VERIFICATION_FAILED, 'No phone number returned from server');
1082
+ }
1083
+ return data;
1084
+ }
1085
+ catch (error) {
1086
+ if (this.isAuthError(error))
1087
+ throw error;
1088
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.NETWORK_ERROR, 'Failed to verify phone number', {
1089
+ originalError: error,
1090
+ context: {
1091
+ step: 'process',
1092
+ timestamp: new Date().toISOString()
1093
+ }
1094
+ });
1095
+ }
1096
+ });
1097
+ }
1098
+ /**
1099
+ * Helper to extract credential string from various formats
1100
+ */
1101
+ extractCredentialString(credentialResponse) {
1102
+ // If already a string, return it
1103
+ if (typeof credentialResponse === 'string') {
1104
+ return credentialResponse;
1105
+ }
1106
+ // Extract from vp_token object - try configured aggregatorId first, then 'glide', then 'default'
1107
+ let credential = credentialResponse[this.config.aggregatorId || 'glide'];
1108
+ // Fallback to 'glide' if not found
1109
+ if (!credential && credentialResponse['glide']) {
1110
+ credential = credentialResponse['glide'];
1111
+ }
1112
+ // Fallback to 'default' if still not found
1113
+ if (!credential && credentialResponse['default']) {
1114
+ credential = credentialResponse['default'];
1115
+ }
1116
+ // If still not found, try to get the first available key
1117
+ if (!credential) {
1118
+ const keys = Object.keys(credentialResponse);
1119
+ if (keys.length > 0) {
1120
+ credential = credentialResponse[keys[0]];
1121
+ }
1122
+ }
1123
+ // Convert array to string if needed
1124
+ return Array.isArray(credential) ? credential[0] : credential;
1125
+ }
1126
+ /**
1127
+ * Helper to extract error details from response
1128
+ */
1129
+ extractErrorDetails(response) {
1130
+ return __awaiter(this, void 0, void 0, function* () {
1131
+ try {
1132
+ const errorData = yield response.json();
1133
+ // Always include the HTTP status from the response
1134
+ return Object.assign(Object.assign({}, errorData), { status: response.status // Ensure HTTP status is included
1135
+ });
1136
+ }
1137
+ catch (_a) {
1138
+ return { status: response.status, statusText: response.statusText };
1139
+ }
1140
+ });
1141
+ }
1142
+ /**
1143
+ * Fetch with timeout
1144
+ */
1145
+ fetchWithTimeout(url, options) {
1146
+ return __awaiter(this, void 0, void 0, function* () {
1147
+ const controller = new AbortController();
1148
+ const timeout = setTimeout(() => controller.abort(), this.config.timeout);
1149
+ try {
1150
+ return yield fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
1151
+ }
1152
+ finally {
1153
+ clearTimeout(timeout);
1154
+ }
1155
+ });
1156
+ }
1157
+ /**
1158
+ * Create an AuthError
1159
+ */
1160
+ createError(code, message, details) {
1161
+ const error = {
1162
+ code,
1163
+ message,
1164
+ details: (details === null || details === void 0 ? void 0 : details.originalError) ? Object.assign(Object.assign({}, details), { originalError: undefined // Remove the original error object
1165
+ }) : details
1166
+ };
1167
+ // Add browser error details if present
1168
+ if (details === null || details === void 0 ? void 0 : details.browserError) {
1169
+ error.browserError = details.browserError;
1170
+ }
1171
+ // Add context if present
1172
+ if (details === null || details === void 0 ? void 0 : details.context) {
1173
+ error.context = details.context;
1174
+ }
1175
+ // Add other specific fields
1176
+ if (details === null || details === void 0 ? void 0 : details.status) {
1177
+ error.status = details.status;
1178
+ }
1179
+ if (details === null || details === void 0 ? void 0 : details.requestId) {
1180
+ error.requestId = details.requestId;
1181
+ }
1182
+ if (details === null || details === void 0 ? void 0 : details.timestamp) {
1183
+ error.timestamp = details.timestamp;
1184
+ }
1185
+ if (details === null || details === void 0 ? void 0 : details.retryAfter) {
1186
+ error.retryAfter = details.retryAfter;
1187
+ }
1188
+ return error;
1189
+ }
1190
+ /**
1191
+ * Type guard for AuthError
1192
+ */
1193
+ isAuthError(error) {
1194
+ return error && typeof error.code === 'string' && typeof error.message === 'string';
1195
+ }
1196
+ /**
1197
+ * Debug logging
1198
+ */
1199
+ log(...args) {
1200
+ if (this.debug) {
1201
+ console.log('[PhoneAuth]', ...args);
1202
+ }
1203
+ }
1204
+ /**
1205
+ * Determine if an error should trigger a retry
1206
+ */
1207
+ shouldRetry(error) {
1208
+ var _a, _b, _c;
1209
+ // Don't retry on 4xx client errors (these are not transient)
1210
+ if (error.status && error.status >= 400 && error.status < 500) {
1211
+ return false;
1212
+ }
1213
+ // Don't retry on explicit user denial or unsupported browser
1214
+ // USER_DENIED cannot be retried automatically because the Digital Credentials API
1215
+ // requires user interaction (transient activation)
1216
+ const nonRetryableCodes = [
1217
+ 'USER_DENIED',
1218
+ 'BROWSER_NOT_SUPPORTED',
1219
+ 'INVALID_PHONE_NUMBER',
1220
+ 'INVALID_PARAMETERS',
1221
+ 'MISSING_PARAMETERS',
1222
+ 'UNPROCESSABLE_ENTITY', // 422 errors
1223
+ 'USE_CASE_MISMATCH', // Use case validation errors
1224
+ 'PHONE_NUMBER_MISMATCH', // Phone verification failures
1225
+ 'VERIFICATION_FAILED', // Verification failures
1226
+ 'INVALID_CREDENTIAL', // Bad credentials
1227
+ 'CARRIER_NOT_ELIGIBLE', // Carrier not supported
1228
+ 'SESSION_EXPIRED', // Session expired
1229
+ 'INVALID_SESSION' // Invalid session
1230
+ ];
1231
+ if (nonRetryableCodes.includes(error.code)) {
1232
+ return false;
1233
+ }
1234
+ // Only retry on network and server errors (5xx)
1235
+ const retryableCodes = [
1236
+ 'NETWORK_ERROR',
1237
+ 'REQUEST_TIMEOUT',
1238
+ 'GATEWAY_TIMEOUT',
1239
+ 'SERVICE_UNAVAILABLE',
1240
+ 'BAD_GATEWAY',
1241
+ 'INTERNAL_SERVER_ERROR'
1242
+ ];
1243
+ // Also check for cross-device error types in details
1244
+ const isCrossDeviceError = ((_a = error.details) === null || _a === void 0 ? void 0 : _a.errorType) && [
1245
+ 'CROSS_DEVICE_TIMEOUT',
1246
+ 'CROSS_DEVICE_CONNECTION_LOST',
1247
+ 'CROSS_DEVICE_INCOMPLETE'
1248
+ ].includes(error.details.errorType);
1249
+ return retryableCodes.includes(error.code) ||
1250
+ isCrossDeviceError ||
1251
+ (((_b = error.browserError) === null || _b === void 0 ? void 0 : _b.name) === 'NetworkError' && ((_c = error.browserError) === null || _c === void 0 ? void 0 : _c.code) !== 19);
1252
+ }
1253
+ /**
1254
+ * Analyze and enhance errors specific to cross-device flows
1255
+ */
1256
+ analyzeCrossDeviceError(error, prepareResponse) {
1257
+ var _a;
1258
+ const errorObj = error;
1259
+ // Check for specific cross-device error patterns
1260
+ const isCrossDeviceTimeout = (errorObj.name === 'AbortError' && this.crossDeviceActive) ||
1261
+ (((_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.includes('timeout')) && this.crossDeviceActive);
1262
+ const isCrossDeviceNetworkIssue = errorObj.name === 'NetworkError' &&
1263
+ errorObj.code !== 19 && // Not user cancellation
1264
+ this.crossDeviceActive;
1265
+ if (isCrossDeviceTimeout) {
1266
+ return {
1267
+ code: 'REQUEST_TIMEOUT',
1268
+ message: 'Cross-device authentication timed out. The QR code may have expired or the phone connection was lost.',
1269
+ details: {
1270
+ suggestion: 'Try again and complete the phone authentication within 2 minutes',
1271
+ originalError: errorObj.message,
1272
+ crossDevice: true,
1273
+ errorType: 'CROSS_DEVICE_TIMEOUT'
1274
+ },
1275
+ browserError: error.browserError
1276
+ };
1277
+ }
1278
+ if (isCrossDeviceNetworkIssue) {
1279
+ return {
1280
+ code: 'NETWORK_ERROR',
1281
+ message: 'Connection lost during cross-device authentication. Please ensure both devices have stable internet.',
1282
+ details: {
1283
+ suggestion: 'Check your network connection on both devices and try again',
1284
+ originalError: errorObj.message,
1285
+ crossDevice: true,
1286
+ errorType: 'CROSS_DEVICE_CONNECTION_LOST'
1287
+ },
1288
+ browserError: error.browserError
1289
+ };
1290
+ }
1291
+ // Return the original error if not cross-device specific
1292
+ return error;
1293
+ }
1294
+ /**
1295
+ * Cache successful session for retry scenarios
1296
+ */
1297
+ cacheSession(options, result) {
1298
+ const cacheKey = this.getCacheKey(options);
1299
+ this.sessionCache.set(cacheKey, {
1300
+ timestamp: Date.now(),
1301
+ data: result
1302
+ });
1303
+ }
1304
+ /**
1305
+ * Retrieve cached session if available and recent
1306
+ */
1307
+ getCachedSession(options) {
1308
+ const cacheKey = this.getCacheKey(options);
1309
+ const cached = this.sessionCache.get(cacheKey);
1310
+ if (!cached)
1311
+ return null;
1312
+ // Cache valid for 5 minutes
1313
+ const cacheValidMs = 5 * 60 * 1000;
1314
+ if (Date.now() - cached.timestamp > cacheValidMs) {
1315
+ this.sessionCache.delete(cacheKey);
1316
+ return null;
1317
+ }
1318
+ return cached.data;
1319
+ }
1320
+ /**
1321
+ * Generate cache key for session storage
1322
+ */
1323
+ getCacheKey(options) {
1324
+ var _a, _b;
1325
+ 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) || ''}`;
1326
+ }
1327
+ /**
1328
+ * Set up periodic cache cleanup
1329
+ */
1330
+ setupCacheCleanup() {
1331
+ // Only run cleanup in browser environment (not during SSR)
1332
+ if (typeof window === 'undefined') {
1333
+ return;
1334
+ }
1335
+ // Clean up expired cache entries every minute
1336
+ setInterval(() => {
1337
+ const now = Date.now();
1338
+ const cacheValidMs = 5 * 60 * 1000;
1339
+ for (const [key, value] of this.sessionCache.entries()) {
1340
+ if (now - value.timestamp > cacheValidMs) {
1341
+ this.sessionCache.delete(key);
1342
+ }
1343
+ }
1344
+ }, 60000);
1345
+ }
1346
+ /**
1347
+ * Utility delay function
1348
+ */
1349
+ delay(ms) {
1350
+ return new Promise(resolve => setTimeout(resolve, ms));
1351
+ }
1352
+ }
1353
+ exports.PhoneAuthClient = PhoneAuthClient;