@glideidentity/web-client-sdk 5.1.3 → 6.0.0-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 (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 +439 -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 +435 -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 +534 -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
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Logger for Phone Authentication SDK.
4
+ *
5
+ * Provides a simple console-based logger with:
6
+ * - Debug mode toggle
7
+ * - PII sanitization
8
+ * - Configurable prefix
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.createLogger = createLogger;
12
+ exports.createNoopLogger = createNoopLogger;
13
+ // ============================================================================
14
+ // PII SANITIZATION
15
+ // ============================================================================
16
+ /**
17
+ * Patterns to detect and sanitize PII.
18
+ */
19
+ const PII_PATTERNS = {
20
+ // Phone numbers: +1234567890 or variations
21
+ phone: /(\+?[1-9]\d{6,14})/g,
22
+ // Email addresses
23
+ email: /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
24
+ // JWT tokens (three base64 segments separated by dots)
25
+ jwt: /(eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*)/g,
26
+ // Session keys (long alphanumeric strings)
27
+ sessionKey: /session[_-]?key["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{20,})["']?/gi,
28
+ };
29
+ /**
30
+ * Sanitize data to remove PII before logging.
31
+ */
32
+ function sanitize(data) {
33
+ if (data === null || data === undefined) {
34
+ return data;
35
+ }
36
+ if (typeof data === 'string') {
37
+ let sanitized = data;
38
+ // Mask phone numbers: +1234567890 -> +1***7890
39
+ sanitized = sanitized.replace(PII_PATTERNS.phone, (match) => {
40
+ if (match.length < 8)
41
+ return match; // Too short to be a phone
42
+ const visible = 4;
43
+ return match.slice(0, 2) + '***' + match.slice(-visible);
44
+ });
45
+ // Mask emails: user@example.com -> u***@example.com
46
+ sanitized = sanitized.replace(PII_PATTERNS.email, (match) => {
47
+ const [local, domain] = match.split('@');
48
+ return local.slice(0, 1) + '***@' + domain;
49
+ });
50
+ // Mask JWTs: eyJ... -> [JWT]
51
+ sanitized = sanitized.replace(PII_PATTERNS.jwt, '[JWT]');
52
+ // Mask session keys
53
+ sanitized = sanitized.replace(PII_PATTERNS.sessionKey, (match, key) => {
54
+ return match.replace(key, key.slice(0, 4) + '***' + key.slice(-4));
55
+ });
56
+ return sanitized;
57
+ }
58
+ if (Array.isArray(data)) {
59
+ return data.map(sanitize);
60
+ }
61
+ if (typeof data === 'object') {
62
+ const sanitized = {};
63
+ for (const [key, value] of Object.entries(data)) {
64
+ // Completely mask sensitive field values
65
+ const sensitiveFields = ['phone_number', 'phoneNumber', 'credential', 'token', 'password', 'secret'];
66
+ if (sensitiveFields.some(f => key.toLowerCase().includes(f.toLowerCase()))) {
67
+ sanitized[key] = typeof value === 'string' ? '[REDACTED]' : value;
68
+ }
69
+ else {
70
+ sanitized[key] = sanitize(value);
71
+ }
72
+ }
73
+ return sanitized;
74
+ }
75
+ return data;
76
+ }
77
+ // ============================================================================
78
+ // LOGGER IMPLEMENTATION
79
+ // ============================================================================
80
+ /**
81
+ * Create a logger instance.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const logger = createLogger({ debug: true });
86
+ * logger.debug('Preparing request', { use_case: 'GetPhoneNumber' });
87
+ * logger.error('Request failed', { code: 'NETWORK_ERROR' });
88
+ * ```
89
+ */
90
+ function createLogger(config = {}) {
91
+ const { debug = false, prefix = '[PhoneAuth]' } = config;
92
+ /**
93
+ * Format a log message.
94
+ */
95
+ function format(message, data) {
96
+ const formattedMessage = `${prefix} ${message}`;
97
+ if (data !== undefined) {
98
+ const sanitizedData = sanitize(data);
99
+ return [formattedMessage, sanitizedData];
100
+ }
101
+ return [formattedMessage];
102
+ }
103
+ return {
104
+ debug(message, data) {
105
+ if (debug) {
106
+ const args = format(message, data);
107
+ console.debug(...args);
108
+ }
109
+ },
110
+ info(message, data) {
111
+ const args = format(message, data);
112
+ console.info(...args);
113
+ },
114
+ warn(message, data) {
115
+ const args = format(message, data);
116
+ console.warn(...args);
117
+ },
118
+ error(message, data) {
119
+ const args = format(message, data);
120
+ console.error(...args);
121
+ },
122
+ };
123
+ }
124
+ /**
125
+ * Create a no-op logger (silent).
126
+ * Useful for testing or when logging is not needed.
127
+ */
128
+ function createNoopLogger() {
129
+ return {
130
+ debug: () => { },
131
+ info: () => { },
132
+ warn: () => { },
133
+ error: () => { },
134
+ };
135
+ }
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ /**
3
+ * Phone Authentication Client
4
+ *
5
+ * Main SDK client for phone number authentication.
6
+ * Supports all strategies: TS43 (Android), Link (iOS), Desktop (QR).
7
+ *
8
+ * IMPORTANT: invokeSecurePrompt must be called from a user gesture (button click)
9
+ * because both iOS App Clips and Android Digital Credentials API require
10
+ * transient activation (user interaction) to open system UI.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.PhoneAuthClient = void 0;
14
+ const core_1 = require("../core");
15
+ const errors_1 = require("../core/errors");
16
+ const http_1 = require("./http");
17
+ const logger_1 = require("./logger");
18
+ const polling_1 = require("./strategies/polling");
19
+ const modal_1 = require("../ui/modal");
20
+ const mobile_debug_console_1 = require("../ui/mobile-debug-console");
21
+ class PhoneAuthClient {
22
+ constructor(config = {}) {
23
+ // Build complete config with defaults
24
+ this.config = {
25
+ ...config,
26
+ endpoints: {
27
+ prepare: config.endpoints?.prepare || '/api/magic-auth/prepare',
28
+ process: config.endpoints?.process || '/api/magic-auth/process',
29
+ polling: config.endpoints?.polling,
30
+ },
31
+ timeout: config.timeout || 30000,
32
+ pollingInterval: config.pollingInterval || 2000,
33
+ maxPollingAttempts: config.maxPollingAttempts || 30,
34
+ debug: config.debug || false,
35
+ };
36
+ // Use custom or default HTTP client
37
+ this.http = config.httpClient || (0, http_1.createHttpClient)({
38
+ timeout: this.config.timeout,
39
+ headers: config.headers,
40
+ dynamicHeaders: config.dynamicHeaders,
41
+ });
42
+ // Use custom or default logger
43
+ this.logger = config.logger || (this.config.debug
44
+ ? (0, logger_1.createLogger)({ debug: true })
45
+ : (0, logger_1.createNoopLogger)());
46
+ this.logger.debug('PhoneAuthClient initialized', {
47
+ endpoints: this.config.endpoints,
48
+ timeout: this.config.timeout,
49
+ });
50
+ // Initialize mobile debug console if configured
51
+ if (config.devtools?.showMobileConsole && typeof window !== 'undefined') {
52
+ mobile_debug_console_1.MobileDebugConsole.init();
53
+ this.logger.info('Mobile debug console enabled');
54
+ }
55
+ }
56
+ // ==========================================================================
57
+ // PUBLIC API - High-Level
58
+ // ==========================================================================
59
+ /**
60
+ * Complete authentication flow in one call.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * // Get phone number
65
+ * const result = await client.authenticate({ use_case: 'GetPhoneNumber' });
66
+ * console.log(result.phone_number);
67
+ *
68
+ * // Verify phone number
69
+ * const result = await client.authenticate({
70
+ * use_case: 'VerifyPhoneNumber',
71
+ * phone_number: '+14155551234'
72
+ * });
73
+ * console.log(result.verified);
74
+ * ```
75
+ */
76
+ async authenticate(request, options) {
77
+ // Step 1: Prepare
78
+ const prepared = await this.prepare(request);
79
+ // Step 2: Invoke
80
+ const invokeResult = await this.invokeSecurePrompt(prepared, options);
81
+ // Step 3: Wait for credential and process
82
+ const credential = await invokeResult.credential;
83
+ // Determine use_case from (in priority order):
84
+ // 1. session.use_case (direct field)
85
+ // 2. session.metadata.use_case (server returns it in metadata)
86
+ // 3. request.use_case (fallback to original request)
87
+ const useCase = prepared.session.use_case
88
+ || prepared.session.metadata?.use_case
89
+ || request.use_case;
90
+ if (!useCase) {
91
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_RESPONSE, 'Could not determine use_case from session or request');
92
+ }
93
+ if (useCase === core_1.USE_CASE.VERIFY_PHONE_NUMBER) {
94
+ return this.verifyPhoneNumber(credential, prepared.session);
95
+ }
96
+ else {
97
+ return this.getPhoneNumber(credential, prepared.session);
98
+ }
99
+ }
100
+ // ==========================================================================
101
+ // PUBLIC API - Granular Steps
102
+ // ==========================================================================
103
+ /**
104
+ * Step 1: Prepare authentication request.
105
+ *
106
+ * Validates input and sends prepare request to backend.
107
+ */
108
+ async prepare(request) {
109
+ this.logger.debug('Preparing authentication', { use_case: request.use_case });
110
+ // Validate phone number if provided
111
+ if (request.phone_number) {
112
+ const phoneValidation = (0, core_1.validatePhoneNumber)(request.phone_number);
113
+ if (!phoneValidation.valid) {
114
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_PHONE_NUMBER, phoneValidation.error);
115
+ }
116
+ }
117
+ // Validate PLMN if provided (deprecated)
118
+ if (request.plmn) {
119
+ const plmnValidation = (0, core_1.validatePlmn)(request.plmn);
120
+ if (!plmnValidation.valid) {
121
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_PLMN, plmnValidation.error);
122
+ }
123
+ }
124
+ // Validate use_case is provided (unless parent_session_id is given)
125
+ if (!request.use_case && !request.options?.parent_session_id) {
126
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.MISSING_PARAMETERS, 'use_case is required');
127
+ }
128
+ // Build request with client info
129
+ const requestBody = {
130
+ ...request,
131
+ id: request.id || this.generateRequestId(),
132
+ client_info: request.client_info || {
133
+ user_agent: navigator.userAgent,
134
+ platform: navigator.platform,
135
+ },
136
+ };
137
+ try {
138
+ const response = await this.http.post(this.config.endpoints.prepare, requestBody);
139
+ this.logger.debug('Prepare successful', {
140
+ strategy: response.authentication_strategy,
141
+ sessionKey: response.session.session_key,
142
+ });
143
+ return response;
144
+ }
145
+ catch (error) {
146
+ this.logger.error('Prepare failed', error);
147
+ throw error;
148
+ }
149
+ }
150
+ /**
151
+ * Step 2: Invoke secure prompt for authentication.
152
+ *
153
+ * IMPORTANT: Must be called from a user gesture (button click).
154
+ *
155
+ * Returns unified InvokeResult with:
156
+ * - credential: Promise that resolves to AuthCredential
157
+ * - cancel?: Function to cancel (Link/Desktop only)
158
+ * - strategy: The authentication strategy used
159
+ * - session: Session info
160
+ */
161
+ async invokeSecurePrompt(prepared, options) {
162
+ const { authentication_strategy: strategy, session, data } = prepared;
163
+ this.logger.debug('Invoking secure prompt', { strategy });
164
+ switch (strategy) {
165
+ case core_1.AUTHENTICATION_STRATEGY.TS43:
166
+ return this.handleTS43(data, session);
167
+ case core_1.AUTHENTICATION_STRATEGY.LINK:
168
+ return this.handleLink(data, session, options);
169
+ case core_1.AUTHENTICATION_STRATEGY.DESKTOP:
170
+ return this.handleDesktop(data, session, options);
171
+ default:
172
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.UNSUPPORTED_STRATEGY, `Unknown strategy: ${strategy}`);
173
+ }
174
+ }
175
+ /**
176
+ * Step 3A: Get phone number from credential.
177
+ */
178
+ async getPhoneNumber(credential, session) {
179
+ this.logger.debug('Getting phone number');
180
+ const requestBody = {
181
+ session,
182
+ credential: credential.credential,
183
+ use_case: core_1.USE_CASE.GET_PHONE_NUMBER,
184
+ };
185
+ try {
186
+ const response = await this.http.post(this.config.endpoints.process, requestBody);
187
+ this.logger.info('Phone number retrieved');
188
+ return response;
189
+ }
190
+ catch (error) {
191
+ this.logger.error('Get phone number failed', error);
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Step 3B: Verify phone number with credential.
197
+ */
198
+ async verifyPhoneNumber(credential, session) {
199
+ this.logger.debug('Verifying phone number');
200
+ const requestBody = {
201
+ session,
202
+ credential: credential.credential,
203
+ use_case: core_1.USE_CASE.VERIFY_PHONE_NUMBER,
204
+ };
205
+ try {
206
+ const response = await this.http.post(this.config.endpoints.process, requestBody);
207
+ this.logger.info('Phone number verified', { verified: response.verified });
208
+ return response;
209
+ }
210
+ catch (error) {
211
+ this.logger.error('Verify phone number failed', error);
212
+ throw error;
213
+ }
214
+ }
215
+ // ==========================================================================
216
+ // PUBLIC API - Utilities
217
+ // ==========================================================================
218
+ /**
219
+ * Check if Digital Credentials API is supported.
220
+ */
221
+ isSupported() {
222
+ if (typeof window === 'undefined')
223
+ return false;
224
+ return 'DigitalCredential' in window;
225
+ }
226
+ /**
227
+ * Get browser support info for debugging.
228
+ */
229
+ getBrowserSupportInfo() {
230
+ if (typeof window === 'undefined') {
231
+ return { supported: false, browser: 'unknown', message: 'Not in browser' };
232
+ }
233
+ const userAgent = navigator.userAgent;
234
+ const isChrome = /Chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor);
235
+ const isEdge = /Edg\//.test(userAgent);
236
+ const supported = this.isSupported();
237
+ if (supported) {
238
+ return {
239
+ supported: true,
240
+ browser: isChrome ? 'Chrome' : isEdge ? 'Edge' : 'Other',
241
+ };
242
+ }
243
+ return {
244
+ supported: false,
245
+ browser: isChrome ? 'Chrome' : isEdge ? 'Edge' : 'Other',
246
+ message: 'Enable chrome://flags/#web-identity-digital-credentials',
247
+ };
248
+ }
249
+ // ==========================================================================
250
+ // PRIVATE - Strategy Handlers
251
+ // ==========================================================================
252
+ /**
253
+ * Handle TS43 strategy (Android Digital Credentials API).
254
+ */
255
+ async handleTS43(data, session) {
256
+ // Check browser support
257
+ if (!this.isSupported()) {
258
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.BROWSER_NOT_SUPPORTED, 'Digital Credentials API not available');
259
+ }
260
+ // Build credential request
261
+ const credentialRequest = {
262
+ digital: {
263
+ requests: [{
264
+ protocol: data.protocol,
265
+ data: data.data,
266
+ }],
267
+ },
268
+ };
269
+ this.logger.debug('Invoking Digital Credentials API');
270
+ // Create credential promise
271
+ const credentialPromise = (async () => {
272
+ try {
273
+ const response = await navigator.credentials.get(credentialRequest);
274
+ const digitalResponse = response;
275
+ if (!digitalResponse?.data?.vp_token) {
276
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_RESPONSE, 'No credential returned from Digital Credentials API');
277
+ }
278
+ // Extract credential from vp_token
279
+ const vpToken = digitalResponse.data.vp_token;
280
+ const credentialValue = typeof vpToken === 'string'
281
+ ? vpToken
282
+ : Object.values(vpToken)[0];
283
+ const credential = Array.isArray(credentialValue)
284
+ ? credentialValue[0]
285
+ : credentialValue;
286
+ // Validate credential is not empty/undefined
287
+ if (!credential || (typeof credential === 'string' && credential.trim() === '')) {
288
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_RESPONSE, 'Empty credential returned from Digital Credentials API');
289
+ }
290
+ return {
291
+ credential: credential,
292
+ session,
293
+ authenticated: true,
294
+ };
295
+ }
296
+ catch (error) {
297
+ // If it's already an AuthError, rethrow it directly
298
+ if ((0, errors_1.isAuthError)(error)) {
299
+ throw error;
300
+ }
301
+ // Map browser errors to SDK errors
302
+ const err = error;
303
+ if (err.name === 'NotAllowedError' ||
304
+ (err.name === 'NetworkError' && err.code === 19)) {
305
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.USER_CANCELLED, 'User cancelled authentication');
306
+ }
307
+ if (err.name === 'NotSupportedError') {
308
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.BROWSER_NOT_SUPPORTED, 'Browser not supported');
309
+ }
310
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.NETWORK_ERROR, err.message || 'Credential request failed');
311
+ }
312
+ })();
313
+ // TS43 returns immediately (no cancel needed)
314
+ return {
315
+ credential: credentialPromise,
316
+ cancel: undefined,
317
+ strategy: core_1.AUTHENTICATION_STRATEGY.TS43,
318
+ session,
319
+ };
320
+ }
321
+ /**
322
+ * Handle Link strategy (iOS App Clips).
323
+ * Uses window.location.href to navigate to the App Clip.
324
+ */
325
+ handleLink(data, session, options) {
326
+ if (!data.url) {
327
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.INVALID_RESPONSE, 'Missing link URL');
328
+ }
329
+ this.logger.debug('Opening App Clip', { url: data.url });
330
+ // Navigate to App Clip URL (must be from user gesture)
331
+ // Using window.location.href for better iOS compatibility
332
+ window.location.href = data.url;
333
+ // Start polling for authentication status
334
+ const polling = (0, polling_1.createPollingHandler)({
335
+ sessionKey: session.session_key,
336
+ interval: options?.pollingInterval || this.config.pollingInterval,
337
+ maxAttempts: options?.maxPollingAttempts || this.config.maxPollingAttempts,
338
+ pollingEndpoint: options?.pollingEndpoint || this.config.endpoints.polling,
339
+ statusUrl: data.status_url,
340
+ logger: this.logger,
341
+ });
342
+ // Create credential promise from polling
343
+ const credentialPromise = polling.start().then(result => {
344
+ if (result.status === 'completed' && result.credential) {
345
+ return {
346
+ credential: result.credential,
347
+ session: result.session || session,
348
+ authenticated: true,
349
+ };
350
+ }
351
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.VERIFICATION_FAILED, result.message || 'Link authentication failed');
352
+ });
353
+ return {
354
+ credential: credentialPromise,
355
+ cancel: () => polling.cancel(),
356
+ strategy: core_1.AUTHENTICATION_STRATEGY.LINK,
357
+ session,
358
+ };
359
+ }
360
+ /**
361
+ * Handle Desktop strategy (QR code).
362
+ */
363
+ handleDesktop(data, session, options) {
364
+ // Extract session ID for polling
365
+ const sessionId = data.data?.session_id || session.session_key;
366
+ this.logger.debug('Starting desktop authentication', { sessionId });
367
+ // Start polling for authentication status
368
+ const polling = (0, polling_1.createPollingHandler)({
369
+ sessionKey: sessionId,
370
+ interval: options?.pollingInterval || this.config.pollingInterval,
371
+ maxAttempts: options?.maxPollingAttempts || this.config.maxPollingAttempts,
372
+ pollingEndpoint: options?.pollingEndpoint || this.config.endpoints.polling,
373
+ statusUrl: data.data?.status_url,
374
+ logger: this.logger,
375
+ });
376
+ // Show modal UI if not prevented
377
+ let modal = null;
378
+ if (!options?.preventDefaultUI) {
379
+ try {
380
+ // Create QR code data from DesktopData
381
+ const qrData = (0, modal_1.createQRCodeDataFromDesktop)(data);
382
+ // Create and show modal
383
+ modal = new modal_1.AuthModal(options?.modalOptions);
384
+ modal.showQRCode(qrData, options?.modalOptions?.description || 'Scan with your phone camera');
385
+ // Set close callback to cancel polling
386
+ modal.setCloseCallback(() => {
387
+ this.logger.debug('Modal closed by user, cancelling authentication');
388
+ polling.cancel();
389
+ });
390
+ this.logger.debug('Desktop modal displayed');
391
+ }
392
+ catch (e) {
393
+ this.logger.warn('Failed to show modal, continuing with polling only', e);
394
+ }
395
+ }
396
+ // Create credential promise from polling
397
+ const credentialPromise = polling.start().then(result => {
398
+ // Close modal on success
399
+ if (modal) {
400
+ modal.close();
401
+ }
402
+ if (result.status === 'completed' && result.credential) {
403
+ return {
404
+ credential: result.credential,
405
+ session: result.session || session,
406
+ authenticated: true,
407
+ };
408
+ }
409
+ throw (0, errors_1.createAuthError)(errors_1.ERROR_CODES.VERIFICATION_FAILED, result.message || 'Desktop authentication failed');
410
+ }).catch(error => {
411
+ // Close modal on error
412
+ if (modal) {
413
+ modal.close();
414
+ }
415
+ throw error;
416
+ });
417
+ return {
418
+ credential: credentialPromise,
419
+ cancel: () => {
420
+ polling.cancel();
421
+ if (modal) {
422
+ modal.close();
423
+ }
424
+ },
425
+ strategy: core_1.AUTHENTICATION_STRATEGY.DESKTOP,
426
+ session,
427
+ };
428
+ }
429
+ // ==========================================================================
430
+ // PRIVATE - Utilities
431
+ // ==========================================================================
432
+ /**
433
+ * Generate a unique request ID.
434
+ */
435
+ generateRequestId() {
436
+ return `web-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
437
+ }
438
+ }
439
+ exports.PhoneAuthClient = PhoneAuthClient;