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