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