@glideidentity/web-client-sdk 5.1.3 → 6.0.0-beta.2

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