@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.
- 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 -161
- 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 -157
- 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
|
@@ -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;
|