@glideidentity/web-client-sdk 5.0.1 → 5.1.1-beta.3
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 +8 -108
- package/dist/adapters/angular/index.js +1 -0
- package/dist/adapters/angular/phone-auth.service.d.ts +18 -0
- package/dist/adapters/angular/phone-auth.service.js +26 -0
- package/dist/adapters/react/index.js +3 -0
- package/dist/adapters/react/useClient.js +1 -0
- package/dist/adapters/react/usePhoneAuth.js +16 -1
- package/dist/adapters/vanilla/client.js +1 -0
- package/dist/adapters/vanilla/index.js +1 -0
- package/dist/adapters/vanilla/phone-auth.js +31 -0
- package/dist/adapters/vue/index.js +4 -0
- package/dist/adapters/vue/useClient.js +5 -0
- package/dist/adapters/vue/usePhoneAuth.js +20 -1
- package/dist/browser/web-client-sdk.min.js +1 -2
- package/dist/browser.js +6 -0
- package/dist/core/client.js +12 -0
- package/dist/core/logger.js +81 -1
- package/dist/core/phone-auth/api-types.d.ts +1 -6
- package/dist/core/phone-auth/api-types.js +83 -0
- package/dist/core/phone-auth/client.js +374 -38
- package/dist/core/phone-auth/error-utils.js +83 -1
- package/dist/core/phone-auth/index.d.ts +1 -1
- package/dist/core/phone-auth/index.js +2 -2
- package/dist/core/phone-auth/status-types.d.ts +78 -0
- package/dist/core/phone-auth/status-types.js +17 -0
- package/dist/core/phone-auth/strategies/desktop.d.ts +2 -0
- package/dist/core/phone-auth/strategies/desktop.js +136 -13
- package/dist/core/phone-auth/strategies/index.d.ts +4 -0
- package/dist/core/phone-auth/strategies/index.js +4 -0
- package/dist/core/phone-auth/strategies/link.d.ts +2 -0
- package/dist/core/phone-auth/strategies/link.js +97 -13
- package/dist/core/phone-auth/strategies/ts43.d.ts +19 -0
- package/dist/core/phone-auth/strategies/ts43.js +33 -2
- package/dist/core/phone-auth/strategies/types.js +4 -0
- package/dist/core/phone-auth/type-guards.js +131 -0
- package/dist/core/phone-auth/types.d.ts +5 -0
- package/dist/core/phone-auth/types.js +32 -0
- package/dist/core/phone-auth/ui/mobile-debug-console.js +28 -2
- package/dist/core/phone-auth/ui/modal.d.ts +55 -33
- package/dist/core/phone-auth/ui/modal.js +422 -889
- package/dist/core/phone-auth/validation-utils.d.ts +0 -9
- package/dist/core/phone-auth/validation-utils.js +34 -25
- package/dist/core/version.js +2 -1
- package/dist/esm/adapters/angular/index.js +1 -0
- package/dist/esm/adapters/angular/phone-auth.service.d.ts +18 -0
- package/dist/esm/adapters/angular/phone-auth.service.js +26 -0
- package/dist/esm/adapters/react/index.js +3 -0
- package/dist/esm/adapters/react/useClient.js +1 -0
- package/dist/esm/adapters/react/usePhoneAuth.js +16 -1
- package/dist/esm/adapters/vanilla/client.js +1 -0
- package/dist/esm/adapters/vanilla/index.js +1 -0
- package/dist/esm/adapters/vanilla/phone-auth.d.ts +24 -0
- package/dist/esm/adapters/vanilla/phone-auth.js +31 -0
- package/dist/esm/adapters/vue/index.js +4 -0
- package/dist/esm/adapters/vue/useClient.js +5 -0
- package/dist/esm/adapters/vue/usePhoneAuth.js +20 -1
- package/dist/esm/browser.js +6 -0
- package/dist/esm/core/client.d.ts +10 -0
- package/dist/esm/core/client.js +12 -0
- package/dist/esm/core/logger.d.ts +53 -0
- package/dist/esm/core/logger.js +81 -1
- package/dist/esm/core/phone-auth/api-types.d.ts +312 -2
- package/dist/esm/core/phone-auth/api-types.js +83 -0
- package/dist/esm/core/phone-auth/client.d.ts +144 -0
- package/dist/esm/core/phone-auth/client.js +375 -39
- package/dist/esm/core/phone-auth/error-utils.d.ts +29 -0
- package/dist/esm/core/phone-auth/error-utils.js +83 -1
- package/dist/esm/core/phone-auth/index.d.ts +1 -1
- package/dist/esm/core/phone-auth/index.js +4 -2
- package/dist/esm/core/phone-auth/status-types.d.ts +78 -0
- package/dist/esm/core/phone-auth/status-types.js +17 -0
- package/dist/esm/core/phone-auth/strategies/desktop.d.ts +65 -0
- package/dist/esm/core/phone-auth/strategies/desktop.js +136 -13
- package/dist/esm/core/phone-auth/strategies/index.d.ts +4 -0
- package/dist/esm/core/phone-auth/strategies/index.js +4 -0
- package/dist/esm/core/phone-auth/strategies/link.d.ts +50 -0
- package/dist/esm/core/phone-auth/strategies/link.js +97 -13
- package/dist/esm/core/phone-auth/strategies/ts43.d.ts +19 -0
- package/dist/esm/core/phone-auth/strategies/ts43.js +33 -2
- package/dist/esm/core/phone-auth/strategies/types.d.ts +13 -0
- package/dist/esm/core/phone-auth/strategies/types.js +4 -0
- package/dist/esm/core/phone-auth/type-guards.d.ts +128 -0
- package/dist/esm/core/phone-auth/type-guards.js +131 -0
- package/dist/esm/core/phone-auth/types.d.ts +113 -0
- package/dist/esm/core/phone-auth/types.js +32 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +4 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +28 -2
- package/dist/esm/core/phone-auth/ui/modal.d.ts +68 -27
- package/dist/esm/core/phone-auth/ui/modal.js +422 -889
- package/dist/esm/core/phone-auth/validation-utils.d.ts +26 -4
- package/dist/esm/core/phone-auth/validation-utils.js +34 -24
- package/dist/esm/core/types.d.ts +35 -0
- package/dist/esm/core/version.js +2 -1
- package/dist/esm/index.js +9 -1
- package/dist/index.js +7 -0
- package/package.json +1 -1
- package/dist/browser/web-client-sdk.min.js.LICENSE.txt +0 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Link Strategy Handler
|
|
4
|
+
* Handles authentication via app links (iOS and Android)
|
|
5
|
+
* Opens authentication app while keeping user on current page
|
|
6
|
+
*/
|
|
2
7
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
8
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
9
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -16,6 +21,10 @@ class LinkHandler {
|
|
|
16
21
|
this.isCancelled = false;
|
|
17
22
|
this.isPollingInProgress = false;
|
|
18
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Invoke link-based authentication
|
|
26
|
+
* Opens authentication app while keeping user on current page
|
|
27
|
+
*/
|
|
19
28
|
invoke(data, options) {
|
|
20
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
30
|
console.log('[Link Auth] 🔗 invoke() called with data:', JSON.stringify(data, null, 2));
|
|
@@ -24,6 +33,7 @@ class LinkHandler {
|
|
|
24
33
|
maxPollingAttempts: options.maxPollingAttempts,
|
|
25
34
|
pollingEndpoint: options.pollingEndpoint
|
|
26
35
|
}) : 'none');
|
|
36
|
+
// Extract link data from prepare response
|
|
27
37
|
const linkData = data.data;
|
|
28
38
|
if (!linkData || !linkData.url) {
|
|
29
39
|
throw new Error('Invalid link data: missing URL');
|
|
@@ -31,23 +41,36 @@ class LinkHandler {
|
|
|
31
41
|
const sessionKey = data.session.session_key;
|
|
32
42
|
console.log('[Link Auth] Session key:', sessionKey);
|
|
33
43
|
console.log('[Link Auth] Link URL:', linkData.url);
|
|
44
|
+
// Open authentication app without navigating away from current page
|
|
34
45
|
this.openAuthenticationLink(linkData.url);
|
|
46
|
+
// Notify that link was opened
|
|
35
47
|
if (options === null || options === void 0 ? void 0 : options.onLinkOpened) {
|
|
36
48
|
options.onLinkOpened();
|
|
37
49
|
}
|
|
50
|
+
// Start polling for authentication status
|
|
51
|
+
// Use constant interval (no exponential backoff)
|
|
38
52
|
return this.startPolling(sessionKey, linkData, options);
|
|
39
53
|
});
|
|
40
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Open authentication link without navigating away
|
|
57
|
+
*/
|
|
41
58
|
openAuthenticationLink(url) {
|
|
59
|
+
// Always use window.open - Works best for App Clips and Android deep links
|
|
60
|
+
// Must be called in direct response to user action to avoid popup blocker
|
|
42
61
|
const newWindow = window.open(url, '_blank');
|
|
43
62
|
if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
|
|
63
|
+
// Popup was blocked - this can happen if not called from user gesture
|
|
44
64
|
console.warn('[LinkHandler] Failed to open app link - popup may have been blocked');
|
|
45
65
|
}
|
|
46
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Start polling for authentication status with constant interval
|
|
69
|
+
*/
|
|
47
70
|
startPolling(sessionKey, linkData, options) {
|
|
48
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
-
const interval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || 2000;
|
|
50
|
-
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30;
|
|
72
|
+
const interval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || 2000; // Fixed 2 second interval
|
|
73
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30; // 1 minute with 2s interval
|
|
51
74
|
let attempts = 0;
|
|
52
75
|
console.log('[Link Auth] 🚀 Starting polling:', {
|
|
53
76
|
sessionKey,
|
|
@@ -57,22 +80,25 @@ class LinkHandler {
|
|
|
57
80
|
});
|
|
58
81
|
return new Promise((resolve, reject) => {
|
|
59
82
|
this.isPollingActive = true;
|
|
60
|
-
this.pollingReject = reject;
|
|
83
|
+
this.pollingReject = reject; // Store reject function for cancel()
|
|
61
84
|
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
62
85
|
if (!this.isPollingActive) {
|
|
63
|
-
return;
|
|
86
|
+
return; // Polling was stopped
|
|
64
87
|
}
|
|
88
|
+
// Skip if another poll is already in progress
|
|
65
89
|
if (this.isPollingInProgress) {
|
|
66
90
|
return;
|
|
67
91
|
}
|
|
68
|
-
let statusUrl = '';
|
|
92
|
+
let statusUrl = ''; // Declare at function scope for catch block access
|
|
69
93
|
try {
|
|
70
94
|
this.isPollingInProgress = true;
|
|
95
|
+
// Check max attempts before making the request
|
|
71
96
|
if (attempts >= maxAttempts) {
|
|
72
97
|
this.stopPolling();
|
|
73
98
|
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
74
99
|
options.onTimeout();
|
|
75
100
|
}
|
|
101
|
+
// Calculate actual timeout duration
|
|
76
102
|
const timeoutSeconds = Math.round((maxAttempts * interval) / 1000);
|
|
77
103
|
const timeoutMessage = timeoutSeconds >= 60
|
|
78
104
|
? `${Math.floor(timeoutSeconds / 60)} minute${Math.floor(timeoutSeconds / 60) > 1 ? 's' : ''}`
|
|
@@ -86,6 +112,11 @@ class LinkHandler {
|
|
|
86
112
|
reject(new Error(`Authentication timeout after ${timeoutMessage}`));
|
|
87
113
|
return;
|
|
88
114
|
}
|
|
115
|
+
// Build public status endpoint URL
|
|
116
|
+
// Use the same priority logic as Desktop strategy:
|
|
117
|
+
// 1. options?.pollingEndpoint (already contains invoke options OR client config from client.ts)
|
|
118
|
+
// 2. Backend-provided status_url from linkData
|
|
119
|
+
// 3. Hardcoded fallback to API server
|
|
89
120
|
let endpoint = options === null || options === void 0 ? void 0 : options.pollingEndpoint;
|
|
90
121
|
let endpointSource = 'options';
|
|
91
122
|
if (!endpoint && linkData.status_url) {
|
|
@@ -96,11 +127,14 @@ class LinkHandler {
|
|
|
96
127
|
console.log(' - options?.pollingEndpoint:', options === null || options === void 0 ? void 0 : options.pollingEndpoint);
|
|
97
128
|
console.log(' - linkData.status_url:', linkData.status_url);
|
|
98
129
|
console.log(' - selected endpoint:', endpoint, 'from source:', endpointSource);
|
|
130
|
+
// Build the status URL based on endpoint format (same as Desktop)
|
|
99
131
|
if (endpoint && (endpoint.startsWith('http://') || endpoint.startsWith('https://'))) {
|
|
132
|
+
// Full URL provided
|
|
100
133
|
if (endpoint.includes('{{session_id}}')) {
|
|
101
134
|
statusUrl = endpoint.replace('{{session_id}}', sessionKey);
|
|
102
135
|
}
|
|
103
136
|
else if (!endpoint.includes(sessionKey)) {
|
|
137
|
+
// If it doesn't already contain the session ID, check if it's a base URL
|
|
104
138
|
const url = new URL(endpoint);
|
|
105
139
|
statusUrl = `${url.protocol}//${url.host}/public/public/status/${sessionKey}`;
|
|
106
140
|
}
|
|
@@ -109,34 +143,49 @@ class LinkHandler {
|
|
|
109
143
|
}
|
|
110
144
|
}
|
|
111
145
|
else if (endpoint && endpoint !== '') {
|
|
146
|
+
// Relative path provided (e.g. '/api/phone-auth/status')
|
|
112
147
|
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
113
148
|
if (endpoint.includes('{{session_id}}')) {
|
|
114
149
|
statusUrl = origin + endpoint.replace('{{session_id}}', sessionKey);
|
|
115
150
|
}
|
|
116
151
|
else {
|
|
152
|
+
// Append session ID to the provided endpoint
|
|
117
153
|
statusUrl = origin + endpoint + '/' + sessionKey;
|
|
118
154
|
}
|
|
119
155
|
}
|
|
120
156
|
else {
|
|
157
|
+
// No endpoint provided - use hardcoded fallback
|
|
121
158
|
statusUrl = `https://api.glideidentity.app/public/public/status/${sessionKey}`;
|
|
122
159
|
endpointSource = 'fallback';
|
|
123
160
|
}
|
|
124
161
|
console.log(`[Link Auth] Using ${endpointSource} endpoint: ${statusUrl}`);
|
|
162
|
+
// Poll public status endpoint - no authentication required
|
|
125
163
|
console.log(`[Link Auth] Polling status (attempt ${attempts}/${maxAttempts}): ${statusUrl}`);
|
|
164
|
+
// Build headers
|
|
165
|
+
const headers = {
|
|
166
|
+
'Accept': 'application/json'
|
|
167
|
+
};
|
|
168
|
+
// Add developer header if devEnv is set
|
|
169
|
+
if (options === null || options === void 0 ? void 0 : options.devEnv) {
|
|
170
|
+
headers['developer'] = options.devEnv;
|
|
171
|
+
console.log(`[Link Auth] Adding developer header: ${options.devEnv}`);
|
|
172
|
+
}
|
|
126
173
|
const response = yield fetch(statusUrl, {
|
|
127
174
|
method: 'GET',
|
|
128
|
-
headers
|
|
129
|
-
'Accept': 'application/json'
|
|
130
|
-
}
|
|
175
|
+
headers
|
|
131
176
|
});
|
|
132
177
|
console.log(`[Link Auth] Poll response - Status: ${response.status}, OK: ${response.ok}`);
|
|
178
|
+
// Handle based on HTTP status code
|
|
133
179
|
if (response.status === 200) {
|
|
180
|
+
// Session is active (pending or completed)
|
|
134
181
|
const result = yield response.json();
|
|
135
182
|
console.log('[Link Auth] Poll response data:', JSON.stringify(result, null, 2));
|
|
136
183
|
if (result.status === 'completed') {
|
|
184
|
+
// Authentication completed successfully
|
|
137
185
|
console.log('[Link Auth] ✅ Authentication COMPLETED! Session:', sessionKey);
|
|
138
186
|
console.log('[Link Auth] Full completion result:', JSON.stringify(result, null, 2));
|
|
139
187
|
this.stopPolling();
|
|
188
|
+
// Authentication completed successfully
|
|
140
189
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
141
190
|
options.onStatusUpdate({
|
|
142
191
|
status: 'authenticated',
|
|
@@ -144,7 +193,8 @@ class LinkHandler {
|
|
|
144
193
|
data: result
|
|
145
194
|
});
|
|
146
195
|
}
|
|
147
|
-
|
|
196
|
+
// Return the authentication result
|
|
197
|
+
this.pollingReject = undefined; // Clear reject function on success
|
|
148
198
|
resolve({
|
|
149
199
|
authenticated: true,
|
|
150
200
|
credential: result.credential || sessionKey,
|
|
@@ -158,8 +208,9 @@ class LinkHandler {
|
|
|
158
208
|
});
|
|
159
209
|
}
|
|
160
210
|
else if (result.status === 'pending') {
|
|
211
|
+
// Continue polling
|
|
161
212
|
console.log('[Link Auth] Status still pending, continuing to poll...');
|
|
162
|
-
attempts++;
|
|
213
|
+
attempts++; // Increment attempts after successful poll
|
|
163
214
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
164
215
|
options.onStatusUpdate({
|
|
165
216
|
status: 'pending',
|
|
@@ -168,11 +219,13 @@ class LinkHandler {
|
|
|
168
219
|
}
|
|
169
220
|
}
|
|
170
221
|
else {
|
|
222
|
+
// Unexpected status value
|
|
171
223
|
console.log('[Link Auth] ⚠️ Unexpected status value:', result.status, 'Full result:', JSON.stringify(result, null, 2));
|
|
172
|
-
attempts++;
|
|
224
|
+
attempts++; // Increment for unexpected status too
|
|
173
225
|
}
|
|
174
226
|
}
|
|
175
227
|
else if (response.status === 410) {
|
|
228
|
+
// Session expired
|
|
176
229
|
console.log('[Link Auth] ❌ Session expired (410)');
|
|
177
230
|
this.stopPolling();
|
|
178
231
|
const errorData = yield response.json().catch(() => ({ message: 'Session expired' }));
|
|
@@ -188,6 +241,7 @@ class LinkHandler {
|
|
|
188
241
|
reject(new Error(errorData.message || 'Session expired'));
|
|
189
242
|
}
|
|
190
243
|
else if (response.status === 422) {
|
|
244
|
+
// Authentication failed
|
|
191
245
|
console.log('[Link Auth] ❌ Authentication failed (422)');
|
|
192
246
|
this.stopPolling();
|
|
193
247
|
const errorData = yield response.json().catch(() => ({ message: 'Authentication failed' }));
|
|
@@ -196,6 +250,7 @@ class LinkHandler {
|
|
|
196
250
|
const errorMsg = isUserCancelled
|
|
197
251
|
? 'User cancelled authentication'
|
|
198
252
|
: (errorData.message || 'Verification failed');
|
|
253
|
+
// Authentication failed
|
|
199
254
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
200
255
|
options.onStatusUpdate({
|
|
201
256
|
status: 'error',
|
|
@@ -205,6 +260,7 @@ class LinkHandler {
|
|
|
205
260
|
reject(new Error(errorMsg));
|
|
206
261
|
}
|
|
207
262
|
else if (response.status === 404) {
|
|
263
|
+
// Session not found
|
|
208
264
|
console.log('[Link Auth] ❌ Session not found (404)');
|
|
209
265
|
this.stopPolling();
|
|
210
266
|
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
@@ -216,6 +272,7 @@ class LinkHandler {
|
|
|
216
272
|
reject(new Error('Session not found'));
|
|
217
273
|
}
|
|
218
274
|
else if (response.status === 400) {
|
|
275
|
+
// Invalid session key
|
|
219
276
|
console.log('[Link Auth] ❌ Invalid session key (400)');
|
|
220
277
|
this.stopPolling();
|
|
221
278
|
const errorData = yield response.json().catch(() => ({ message: 'Invalid session key' }));
|
|
@@ -223,8 +280,9 @@ class LinkHandler {
|
|
|
223
280
|
reject(new Error(errorData.message || 'Invalid session key'));
|
|
224
281
|
}
|
|
225
282
|
else {
|
|
283
|
+
// Unexpected status - continue polling
|
|
226
284
|
console.log('[Link Auth] ⚠️ Unexpected HTTP status:', response.status, 'continuing to poll...');
|
|
227
|
-
attempts++;
|
|
285
|
+
attempts++; // Increment for unexpected HTTP status
|
|
228
286
|
try {
|
|
229
287
|
const body = yield response.text();
|
|
230
288
|
console.log('[Link Auth] Response body:', body);
|
|
@@ -235,8 +293,9 @@ class LinkHandler {
|
|
|
235
293
|
}
|
|
236
294
|
}
|
|
237
295
|
catch (error) {
|
|
296
|
+
// Network or other error - continue polling
|
|
238
297
|
console.error('[Link Auth] 🔴 Polling error:', error.message || error);
|
|
239
|
-
attempts++;
|
|
298
|
+
attempts++; // Increment for error case
|
|
240
299
|
console.error('[Link Auth] Error details:', {
|
|
241
300
|
name: error.name,
|
|
242
301
|
message: error.message,
|
|
@@ -245,6 +304,7 @@ class LinkHandler {
|
|
|
245
304
|
attempt: attempts,
|
|
246
305
|
error: error
|
|
247
306
|
});
|
|
307
|
+
// Check if it's a CORS error (common on mobile)
|
|
248
308
|
if (error.message && error.message.toLowerCase().includes('failed')) {
|
|
249
309
|
console.error('[Link Auth] ⚠️ Possible CORS issue. Status URL:', statusUrl);
|
|
250
310
|
console.error('[Link Auth] Make sure the API endpoint allows CORS from your ngrok domain');
|
|
@@ -257,14 +317,20 @@ class LinkHandler {
|
|
|
257
317
|
}
|
|
258
318
|
}
|
|
259
319
|
finally {
|
|
320
|
+
// Always clear the polling flag when done
|
|
260
321
|
this.isPollingInProgress = false;
|
|
261
322
|
}
|
|
262
323
|
});
|
|
324
|
+
// Start initial poll immediately
|
|
263
325
|
poll();
|
|
326
|
+
// Set up constant interval polling (no backoff)
|
|
264
327
|
this.pollingInterval = setInterval(poll, interval);
|
|
265
328
|
});
|
|
266
329
|
});
|
|
267
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Stop polling
|
|
333
|
+
*/
|
|
268
334
|
stopPolling() {
|
|
269
335
|
console.log('[Link Auth] 🏁 Stopping polling');
|
|
270
336
|
this.isPollingActive = false;
|
|
@@ -274,6 +340,9 @@ class LinkHandler {
|
|
|
274
340
|
this.pollingInterval = undefined;
|
|
275
341
|
}
|
|
276
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Format response for backend processing
|
|
345
|
+
*/
|
|
277
346
|
formatResponse(response) {
|
|
278
347
|
if (!response.authenticated || !response.credential) {
|
|
279
348
|
throw new Error('Authentication not completed');
|
|
@@ -284,25 +353,40 @@ class LinkHandler {
|
|
|
284
353
|
type: 'link'
|
|
285
354
|
};
|
|
286
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Check if link strategy is supported
|
|
358
|
+
* Returns true for mobile devices (iOS and Android)
|
|
359
|
+
*/
|
|
287
360
|
isSupported() {
|
|
361
|
+
// Link strategy is supported on mobile devices
|
|
288
362
|
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
289
363
|
return isMobile;
|
|
290
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Clean up resources (stop polling if active)
|
|
367
|
+
*/
|
|
291
368
|
cleanup() {
|
|
292
369
|
this.stopPolling();
|
|
293
370
|
this.isCancelled = false;
|
|
294
371
|
this.onCancel = undefined;
|
|
295
372
|
this.pollingReject = undefined;
|
|
296
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Check if polling is currently active
|
|
376
|
+
*/
|
|
297
377
|
isPolling() {
|
|
298
378
|
return this.pollingInterval !== undefined;
|
|
299
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Cancel the ongoing authentication
|
|
382
|
+
*/
|
|
300
383
|
cancel() {
|
|
301
384
|
var _a;
|
|
302
385
|
console.log('[Link Auth] Cancelling authentication');
|
|
303
386
|
this.isCancelled = true;
|
|
304
387
|
this.stopPolling();
|
|
305
388
|
(_a = this.onCancel) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
389
|
+
// Immediately reject the polling promise
|
|
306
390
|
if (this.pollingReject) {
|
|
307
391
|
this.pollingReject({
|
|
308
392
|
code: 'USER_DENIED',
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS-43 Strategy Handler
|
|
3
|
+
* Handles Digital Credentials API authentication for Android/Chromium
|
|
4
|
+
* Properly manages session objects with session_key and nonce
|
|
5
|
+
*/
|
|
1
6
|
import type { StrategyHandler } from './types';
|
|
2
7
|
import type { PrepareResponse } from '../types';
|
|
3
8
|
export declare class TS43Handler implements StrategyHandler {
|
|
9
|
+
/**
|
|
10
|
+
* Invoke TS-43 authentication using Digital Credentials API
|
|
11
|
+
* The data structure from backend is already in the correct format for navigator.credentials.get
|
|
12
|
+
*/
|
|
4
13
|
invoke(data: PrepareResponse): Promise<any>;
|
|
14
|
+
/**
|
|
15
|
+
* Format the credential response for backend processing
|
|
16
|
+
* Include the session key so backend can retrieve the full session
|
|
17
|
+
*/
|
|
5
18
|
formatResponse(response: any): any;
|
|
19
|
+
/**
|
|
20
|
+
* Check if Digital Credentials API is supported
|
|
21
|
+
*/
|
|
6
22
|
isSupported(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Get browser support information
|
|
25
|
+
*/
|
|
7
26
|
getBrowserSupportInfo(): {
|
|
8
27
|
supported: boolean;
|
|
9
28
|
browser: string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TS-43 Strategy Handler
|
|
4
|
+
* Handles Digital Credentials API authentication for Android/Chromium
|
|
5
|
+
* Properly manages session objects with session_key and nonce
|
|
6
|
+
*/
|
|
2
7
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
8
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
9
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -11,31 +16,43 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
17
|
exports.TS43Handler = void 0;
|
|
13
18
|
class TS43Handler {
|
|
19
|
+
/**
|
|
20
|
+
* Invoke TS-43 authentication using Digital Credentials API
|
|
21
|
+
* The data structure from backend is already in the correct format for navigator.credentials.get
|
|
22
|
+
*/
|
|
14
23
|
invoke(data) {
|
|
15
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
// Validate session structure - only session_key is actually required
|
|
16
26
|
if (!data.session || !data.session.session_key) {
|
|
17
27
|
throw new Error('Invalid TS43 session: missing required session_key');
|
|
18
28
|
}
|
|
29
|
+
// Check if Digital Credentials API is available
|
|
19
30
|
if (!this.isSupported()) {
|
|
20
31
|
throw new Error('Digital Credentials API not supported in this browser');
|
|
21
32
|
}
|
|
33
|
+
// Extract TS43 data from the prepare response
|
|
22
34
|
const ts43Data = data.data;
|
|
35
|
+
// The data is already in the correct format from backend
|
|
36
|
+
// Just pass it through to navigator.credentials.get
|
|
23
37
|
const credentialRequest = {
|
|
24
38
|
digital: {
|
|
25
39
|
requests: [{
|
|
26
|
-
protocol: ts43Data.protocol,
|
|
27
|
-
data: ts43Data.data
|
|
40
|
+
protocol: ts43Data.protocol, // e.g., "openid4vp-v1-unsigned"
|
|
41
|
+
data: ts43Data.data // Pass the entire data object as-is
|
|
28
42
|
}]
|
|
29
43
|
}
|
|
30
44
|
};
|
|
31
45
|
try {
|
|
46
|
+
// @ts-ignore - Digital Credentials API types not yet in TypeScript
|
|
32
47
|
const credential = yield navigator.credentials.get(credentialRequest);
|
|
33
48
|
if (!credential) {
|
|
34
49
|
throw new Error('No credential received from Digital Credentials API');
|
|
35
50
|
}
|
|
51
|
+
// @ts-ignore - credential.data is not typed yet
|
|
36
52
|
return credential;
|
|
37
53
|
}
|
|
38
54
|
catch (error) {
|
|
55
|
+
// Handle browser-specific errors
|
|
39
56
|
if (error instanceof Error) {
|
|
40
57
|
if (error.name === 'NotAllowedError') {
|
|
41
58
|
throw new Error('User denied the authentication request');
|
|
@@ -51,8 +68,13 @@ class TS43Handler {
|
|
|
51
68
|
}
|
|
52
69
|
});
|
|
53
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Format the credential response for backend processing
|
|
73
|
+
* Include the session key so backend can retrieve the full session
|
|
74
|
+
*/
|
|
54
75
|
formatResponse(response) {
|
|
55
76
|
var _a;
|
|
77
|
+
// Extract vp_token from the response
|
|
56
78
|
const vpToken = (_a = response.data) === null || _a === void 0 ? void 0 : _a.vp_token;
|
|
57
79
|
if (!vpToken) {
|
|
58
80
|
throw new Error('Invalid TS43 response: missing vp_token');
|
|
@@ -62,12 +84,20 @@ class TS43Handler {
|
|
|
62
84
|
type: 'ts43'
|
|
63
85
|
};
|
|
64
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if Digital Credentials API is supported
|
|
89
|
+
*/
|
|
65
90
|
isSupported() {
|
|
66
91
|
if (typeof window === 'undefined') {
|
|
67
92
|
return false;
|
|
68
93
|
}
|
|
94
|
+
// Check for DigitalCredential constructor
|
|
95
|
+
// @ts-ignore - DigitalCredential not yet in TypeScript
|
|
69
96
|
return 'DigitalCredential' in window;
|
|
70
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Get browser support information
|
|
100
|
+
*/
|
|
71
101
|
getBrowserSupportInfo() {
|
|
72
102
|
if (typeof window === 'undefined') {
|
|
73
103
|
return {
|
|
@@ -87,6 +117,7 @@ class TS43Handler {
|
|
|
87
117
|
browser: isChrome ? 'Chrome' : isEdge ? 'Edge' : 'Chromium'
|
|
88
118
|
};
|
|
89
119
|
}
|
|
120
|
+
// Provide specific guidance based on browser
|
|
90
121
|
if ((isChrome || isEdge) && isAndroid) {
|
|
91
122
|
return {
|
|
92
123
|
supported: false,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Type Guards and Helper Functions for Phone Authentication
|
|
4
|
+
*
|
|
5
|
+
* These utilities help developers work with the SDK responses in a type-safe way
|
|
6
|
+
* without having to write their own type checking logic.
|
|
7
|
+
*/
|
|
2
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
9
|
exports.requiresUserAction = exports.requiresPolling = exports.isHeadlessResult = void 0;
|
|
4
10
|
exports.isExtendedResponse = isExtendedResponse;
|
|
@@ -10,6 +16,24 @@ exports.isDesktopStrategy = isDesktopStrategy;
|
|
|
10
16
|
exports.getStrategy = getStrategy;
|
|
11
17
|
exports.hasPollingControls = hasPollingControls;
|
|
12
18
|
exports.hasTrigger = hasTrigger;
|
|
19
|
+
/**
|
|
20
|
+
* Type guard to check if the result is an ExtendedResponse (extended mode)
|
|
21
|
+
* or a Credential (standard mode).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const result = await invokeSecurePrompt(sdkRequest, { executionMode: 'extended' });
|
|
26
|
+
*
|
|
27
|
+
* if (isExtendedResponse(result)) {
|
|
28
|
+
* // TypeScript knows this is ExtendedResponse
|
|
29
|
+
* console.log(result.strategy);
|
|
30
|
+
* await result.cancel();
|
|
31
|
+
* } else {
|
|
32
|
+
* // TypeScript knows this is a Credential
|
|
33
|
+
* const processedResult = await verifyPhoneNumberCredential(result, session);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
13
37
|
function isExtendedResponse(result) {
|
|
14
38
|
return result &&
|
|
15
39
|
typeof result === 'object' &&
|
|
@@ -18,16 +42,41 @@ function isExtendedResponse(result) {
|
|
|
18
42
|
'cancel' in result &&
|
|
19
43
|
typeof result.cancel === 'function';
|
|
20
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to check if the result is a Credential (standard mode response).
|
|
47
|
+
* A credential is either a string token or an object without ExtendedResponse properties.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* if (isCredential(result)) {
|
|
52
|
+
* // Process the credential directly
|
|
53
|
+
* const verified = await verifyPhoneNumberCredential(result, session);
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
21
57
|
function isCredential(result) {
|
|
22
58
|
if (!result)
|
|
23
59
|
return false;
|
|
60
|
+
// String credentials are valid
|
|
24
61
|
if (typeof result === 'string')
|
|
25
62
|
return true;
|
|
63
|
+
// Object credentials should NOT have ExtendedResponse properties
|
|
26
64
|
if (typeof result === 'object') {
|
|
27
65
|
return !('strategy' in result) && !('credential' in result) && !('cancel' in result);
|
|
28
66
|
}
|
|
29
67
|
return false;
|
|
30
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Type guard to check if the result is an AuthCredential object.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* if (isAuthCredential(result)) {
|
|
75
|
+
* console.log(result.credential);
|
|
76
|
+
* console.log(result.authenticated);
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
31
80
|
function isAuthCredential(result) {
|
|
32
81
|
return result &&
|
|
33
82
|
typeof result === 'object' &&
|
|
@@ -35,33 +84,115 @@ function isAuthCredential(result) {
|
|
|
35
84
|
'authenticated' in result &&
|
|
36
85
|
'session' in result;
|
|
37
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Type guard to check if an ExtendedResponse is using the Link strategy.
|
|
89
|
+
* Link strategy involves opening an app link (App Clip on iOS, app on Android).
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* if (isExtendedResponse(result) && isLinkStrategy(result)) {
|
|
94
|
+
* // Re-trigger app opening if needed
|
|
95
|
+
* result.trigger();
|
|
96
|
+
* await result.credential;
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
38
100
|
function isLinkStrategy(result) {
|
|
39
101
|
return result.strategy === 'link';
|
|
40
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Type guard to check if an ExtendedResponse is using the TS43 strategy.
|
|
105
|
+
* TS43 strategy uses the browser's Digital Credentials API.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* if (isExtendedResponse(result) && isTS43Strategy(result)) {
|
|
110
|
+
* // Re-trigger credential request if needed
|
|
111
|
+
* await result.trigger();
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
41
115
|
function isTS43Strategy(result) {
|
|
42
116
|
return result.strategy === 'ts43';
|
|
43
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Type guard to check if an ExtendedResponse is using the Desktop strategy.
|
|
120
|
+
* Desktop strategy involves QR codes for cross-device authentication.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* if (isExtendedResponse(result) && isDesktopStrategy(result)) {
|
|
125
|
+
* // Show custom QR code UI
|
|
126
|
+
* displayQRCode(result.qr_code_data);
|
|
127
|
+
* await result.start_polling();
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
44
131
|
function isDesktopStrategy(result) {
|
|
45
132
|
return result.strategy === 'desktop';
|
|
46
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Helper function to safely get the authentication strategy from any result.
|
|
136
|
+
* Returns undefined if the result is not an ExtendedResponse.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const strategy = getStrategy(result);
|
|
141
|
+
* if (strategy === 'link') {
|
|
142
|
+
* // Handle link strategy
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
47
146
|
function getStrategy(result) {
|
|
48
147
|
if (isExtendedResponse(result)) {
|
|
49
148
|
return result.strategy;
|
|
50
149
|
}
|
|
51
150
|
return undefined;
|
|
52
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Helper function to determine if a result has polling controls.
|
|
154
|
+
* Link and Desktop strategies have polling controls in extended mode.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* if (hasPollingControls(result)) {
|
|
159
|
+
* await result.start_polling();
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
53
163
|
function hasPollingControls(result) {
|
|
54
164
|
if (!isExtendedResponse(result))
|
|
55
165
|
return false;
|
|
56
166
|
return (result.strategy === 'link' && 'start_polling' in result) ||
|
|
57
167
|
(result.strategy === 'desktop' && 'start_polling' in result);
|
|
58
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Helper function to determine if a result has a trigger method.
|
|
171
|
+
* Link and TS43 strategies have trigger methods in extended mode.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* if (hasTrigger(result)) {
|
|
176
|
+
* result.trigger();
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
59
180
|
function hasTrigger(result) {
|
|
60
181
|
if (!isExtendedResponse(result))
|
|
61
182
|
return false;
|
|
62
183
|
return 'trigger' in result && typeof result.trigger === 'function';
|
|
63
184
|
}
|
|
185
|
+
// Export legacy function names as deprecated aliases for backward compatibility
|
|
186
|
+
/**
|
|
187
|
+
* @deprecated Use isExtendedResponse instead
|
|
188
|
+
*/
|
|
64
189
|
exports.isHeadlessResult = isExtendedResponse;
|
|
190
|
+
/**
|
|
191
|
+
* @deprecated Use hasPollingControls instead
|
|
192
|
+
*/
|
|
65
193
|
exports.requiresPolling = hasPollingControls;
|
|
194
|
+
/**
|
|
195
|
+
* @deprecated This function is no longer needed as extended mode handles user actions differently
|
|
196
|
+
*/
|
|
66
197
|
const requiresUserAction = (result) => false;
|
|
67
198
|
exports.requiresUserAction = requiresUserAction;
|
|
@@ -80,6 +80,11 @@ export interface AuthConfig extends PhoneAuthCallbacks {
|
|
|
80
80
|
* @default false
|
|
81
81
|
*/
|
|
82
82
|
debug?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Developer environment for testing (e.g., 'dev1', 'dev2')
|
|
85
|
+
* When set, adds a 'developer' header to polling requests
|
|
86
|
+
*/
|
|
87
|
+
devEnv?: string;
|
|
83
88
|
/**
|
|
84
89
|
* Developer tools configuration for debugging
|
|
85
90
|
*/
|