@glideidentity/web-client-sdk 4.4.8-beta.2 → 4.4.8
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 +395 -714
- package/dist/browser/web-client-sdk.min.js +1 -1
- package/dist/core/phone-auth/client.js +195 -75
- package/dist/core/phone-auth/strategies/desktop.d.ts +9 -3
- package/dist/core/phone-auth/strategies/desktop.js +47 -4
- package/dist/core/phone-auth/strategies/link.d.ts +8 -2
- package/dist/core/phone-auth/strategies/link.js +142 -14
- package/dist/core/phone-auth/types.d.ts +1 -1
- package/dist/core/phone-auth/ui/modal.d.ts +4 -0
- package/dist/core/phone-auth/ui/modal.js +17 -6
- package/dist/core/version.js +1 -1
- package/dist/esm/core/phone-auth/client.js +195 -75
- package/dist/esm/core/phone-auth/strategies/desktop.d.ts +9 -3
- package/dist/esm/core/phone-auth/strategies/desktop.js +47 -4
- package/dist/esm/core/phone-auth/strategies/link.d.ts +8 -2
- package/dist/esm/core/phone-auth/strategies/link.js +142 -14
- package/dist/esm/core/phone-auth/types.d.ts +1 -1
- package/dist/esm/core/phone-auth/ui/modal.d.ts +4 -0
- package/dist/esm/core/phone-auth/ui/modal.js +17 -6
- package/dist/esm/core/version.js +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,703 +7,321 @@ The official web SDK for integrating Glide's carrier-grade phone verification in
|
|
|
7
7
|
- 🚀 **Instant Verification**: Direct carrier verification without SMS
|
|
8
8
|
- 🔐 **Fraud Resistant**: Can't be intercepted or spoofed like SMS codes
|
|
9
9
|
- ⚡ **Real-time Progress**: Track verification steps with detailed state management
|
|
10
|
-
- 📱 **
|
|
11
|
-
-
|
|
12
|
-
- 🔄 **Unified
|
|
10
|
+
- 📱 **Multi-Strategy Support**: Desktop QR code, mobile App Clips, and Android deep links
|
|
11
|
+
- 🎯 **Extended Mode**: Granular control over authentication flow
|
|
12
|
+
- 🔄 **Unified API**: Consistent interface for all authentication strategies
|
|
13
13
|
- 🌐 **Framework Support**: React, Vue 3, Angular, and vanilla JS/TS
|
|
14
14
|
- 🛡️ **Type Safe**: Full TypeScript support with type guards and IntelliSense
|
|
15
15
|
- 🎯 **Developer Friendly**: Clear error messages and browser compatibility checks
|
|
16
16
|
|
|
17
17
|
## What's New in v4.4
|
|
18
18
|
|
|
19
|
-
### 🎉
|
|
20
|
-
- **
|
|
19
|
+
### 🎉 Enhanced Control & Better DX
|
|
20
|
+
- **Extended Mode**: More control with `executionMode: 'extended'` for granular authentication flow
|
|
21
|
+
- **Prevent Default UI**: Use `preventDefaultUI: true` to disable all SDK modals
|
|
21
22
|
- **Type Guards**: Helper functions for type-safe result handling
|
|
22
23
|
- **Unified Trigger Pattern**: Consistent `trigger()` function for all strategies
|
|
23
24
|
- **Auto-Trigger Support**: Automatic App Clip opening with retry capability
|
|
24
25
|
- **TypeScript Enhancements**: Full IntelliSense and type safety
|
|
25
|
-
- **
|
|
26
|
+
- **Faster Timeout**: Polling timeout reduced to 1 minute for better UX
|
|
27
|
+
- **Dual-Platform QR**: Support for iOS and Android specific QR codes
|
|
26
28
|
|
|
27
29
|
## Table of Contents
|
|
28
30
|
|
|
29
31
|
- [Installation](#installation)
|
|
30
32
|
- [Quick Start](#quick-start)
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
+
- [Extended Mode - Advanced Control](#extended-mode---advanced-control)
|
|
34
|
+
- [Authentication Strategies](#authentication-strategies)
|
|
33
35
|
- [Framework Examples](#framework-examples)
|
|
34
36
|
- [Error Handling](#error-handling)
|
|
35
|
-
- [Advanced Features](#advanced-features)
|
|
36
37
|
- [Browser Support](#browser-support)
|
|
37
38
|
- [API Reference](#api-reference)
|
|
38
39
|
|
|
39
40
|
## Installation
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
npm install
|
|
43
|
+
npm install @glideidentity/web-client-sdk
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
## Quick Start
|
|
46
47
|
|
|
47
|
-
### React
|
|
48
|
+
### React Example
|
|
48
49
|
|
|
49
50
|
```jsx
|
|
50
|
-
import {
|
|
51
|
+
import React, { useState } from 'react';
|
|
52
|
+
import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
|
|
51
53
|
|
|
52
54
|
function PhoneVerification() {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
const [phoneAuth] = useState(() => new PhoneAuthClient({
|
|
56
|
+
pollingInterval: 2000,
|
|
57
|
+
maxPollingAttempts: 30,
|
|
58
|
+
debug: false
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const handleGetPhoneNumber = async () => {
|
|
62
|
+
try {
|
|
63
|
+
// Prepare the request
|
|
64
|
+
const prepareResult = await phoneAuth.preparePhoneRequest({
|
|
65
|
+
use_case: 'GetPhoneNumber'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Invoke authentication (SDK handles UI by default)
|
|
69
|
+
const credential = await phoneAuth.invokeSecurePrompt(prepareResult);
|
|
70
|
+
|
|
71
|
+
// Process the credential
|
|
72
|
+
const result = await phoneAuth.getPhoneNumberCredential(
|
|
73
|
+
credential,
|
|
74
|
+
prepareResult.session
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
console.log('Phone number:', result.phone_number);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Verification failed:', error);
|
|
73
80
|
}
|
|
74
|
-
}
|
|
81
|
+
};
|
|
75
82
|
|
|
76
|
-
const
|
|
83
|
+
const handleVerifyPhoneNumber = async () => {
|
|
77
84
|
try {
|
|
78
|
-
//
|
|
79
|
-
await
|
|
85
|
+
// Prepare with phone number
|
|
86
|
+
const prepareResult = await phoneAuth.preparePhoneRequest({
|
|
87
|
+
use_case: 'VerifyPhoneNumber',
|
|
88
|
+
phone_number: '+14155551234'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Invoke authentication
|
|
92
|
+
const credential = await phoneAuth.invokeSecurePrompt(prepareResult);
|
|
93
|
+
|
|
94
|
+
// Verify the number
|
|
95
|
+
const result = await phoneAuth.verifyPhoneNumberCredential(
|
|
96
|
+
credential,
|
|
97
|
+
prepareResult.session
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log('Verified:', result.verified);
|
|
80
101
|
} catch (error) {
|
|
81
|
-
|
|
82
|
-
console.error('Verification failed:', error)
|
|
102
|
+
console.error('Verification failed:', error);
|
|
83
103
|
}
|
|
84
|
-
}
|
|
104
|
+
};
|
|
85
105
|
|
|
86
106
|
return (
|
|
87
107
|
<div>
|
|
88
|
-
<button onClick={
|
|
89
|
-
|
|
108
|
+
<button onClick={handleGetPhoneNumber}>
|
|
109
|
+
Get Phone Number
|
|
110
|
+
</button>
|
|
111
|
+
<button onClick={handleVerifyPhoneNumber}>
|
|
112
|
+
Verify Phone Number
|
|
90
113
|
</button>
|
|
91
|
-
|
|
92
|
-
{/* Manual retry button (NEW) */}
|
|
93
|
-
{error && !isLoading && (
|
|
94
|
-
<button onClick={retryLastRequest}>
|
|
95
|
-
Retry
|
|
96
|
-
</button>
|
|
97
|
-
)}
|
|
98
|
-
|
|
99
|
-
{result && <p>Phone verified: {result.phone_number}</p>}
|
|
100
114
|
</div>
|
|
101
|
-
)
|
|
115
|
+
);
|
|
102
116
|
}
|
|
103
117
|
```
|
|
104
118
|
|
|
105
|
-
##
|
|
106
|
-
|
|
107
|
-
The SDK now supports two distinct modes for authentication, giving you complete control over the user experience:
|
|
108
|
-
|
|
109
|
-
### **UI Mode (Default)** - Ready-to-Use Components
|
|
110
|
-
The SDK provides built-in modals and buttons for authentication. Perfect for quick integration.
|
|
119
|
+
## Extended Mode - Advanced Control
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
Returns raw data without any UI, allowing you to build completely custom experiences.
|
|
121
|
+
The SDK supports an **Extended Mode** that provides granular control over the authentication flow. This mode returns an `ExtendedResponse` object with additional methods and data for custom implementations.
|
|
114
122
|
|
|
115
|
-
### When to Use
|
|
123
|
+
### When to Use Extended Mode
|
|
116
124
|
|
|
117
|
-
| Use Case | Mode |
|
|
118
|
-
|
|
119
|
-
|
|
|
120
|
-
| Custom
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
125
|
+
| Use Case | Standard Mode | Extended Mode |
|
|
126
|
+
|----------|--------------|---------------|
|
|
127
|
+
| Simple verification | ✓ Best choice | Use if needed |
|
|
128
|
+
| Custom retry logic | Limited | ✓ Full control |
|
|
129
|
+
| Progressive UI updates | No | ✓ Real-time status |
|
|
130
|
+
| Custom polling control | No | ✓ Start/stop control |
|
|
131
|
+
| Multiple auth attempts | Manual | ✓ Built-in retry |
|
|
124
132
|
|
|
125
|
-
###
|
|
133
|
+
### Extended Mode Example
|
|
126
134
|
|
|
127
135
|
```javascript
|
|
128
|
-
//
|
|
129
|
-
const prepareResult = await phoneAuth.preparePhoneRequest({
|
|
130
|
-
use_case: 'VerifyPhoneNumber',
|
|
131
|
-
phone_number: '+14155551234'
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Behavior varies by strategy:
|
|
135
|
-
// - Desktop: Shows QR code modal
|
|
136
|
-
// - Link: Shows button modal to open app
|
|
137
|
-
// - TS43: Directly invokes Digital Credentials API (no modal - OS provides UI)
|
|
138
|
-
const credential = await phoneAuth.invokeSecurePrompt(prepareResult);
|
|
139
|
-
|
|
140
|
-
// Or customize the built-in UI
|
|
141
|
-
const credential = await phoneAuth.invokeSecurePrompt(prepareResult, {
|
|
142
|
-
modalOptions: {
|
|
143
|
-
title: 'Verify Your Identity',
|
|
144
|
-
description: 'Complete verification to continue',
|
|
145
|
-
buttonText: 'Verify with Verizon',
|
|
146
|
-
className: 'my-custom-modal'
|
|
147
|
-
},
|
|
148
|
-
callbacks: {
|
|
149
|
-
onOpen: () => console.log('Modal opened'),
|
|
150
|
-
onAuthComplete: (result) => console.log('Success!', result)
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Headless Mode Example
|
|
156
|
-
|
|
157
|
-
```javascript
|
|
158
|
-
// Get raw data without any UI
|
|
159
|
-
const prepareResult = await phoneAuth.preparePhoneRequest({
|
|
160
|
-
use_case: 'VerifyPhoneNumber',
|
|
161
|
-
phone_number: '+14155551234'
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Returns data instead of showing UI
|
|
136
|
+
// Enable extended mode for granular control
|
|
165
137
|
const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
|
|
166
|
-
|
|
138
|
+
executionMode: 'extended',
|
|
139
|
+
preventDefaultUI: true // Optional: disable all SDK UI
|
|
167
140
|
});
|
|
168
141
|
|
|
169
|
-
//
|
|
170
|
-
if (result.strategy === '
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Open in your custom way
|
|
175
|
-
window.open(result.url, '_blank');
|
|
142
|
+
// Extended response provides more control
|
|
143
|
+
if (result.strategy === 'desktop') {
|
|
144
|
+
// Custom QR display
|
|
145
|
+
displayQR(result.qr_code_data.ios_qr_image);
|
|
176
146
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
// Desktop - QR code
|
|
182
|
-
console.log('QR code image:', result.qrCode);
|
|
183
|
-
|
|
184
|
-
// Display QR in your custom UI
|
|
185
|
-
document.getElementById('qr').src = result.qrCode;
|
|
186
|
-
|
|
187
|
-
// Wait for mobile scan completion
|
|
188
|
-
const credential = await result.pollingPromise;
|
|
189
|
-
|
|
190
|
-
} else if (result.strategy === 'ts43') {
|
|
191
|
-
// Android/Chrome - Digital Credentials
|
|
192
|
-
console.log('Ready to invoke Digital Credentials');
|
|
147
|
+
// Start polling when ready (only if preventDefaultUI was true)
|
|
148
|
+
if (result.start_polling) {
|
|
149
|
+
const credential = await result.start_polling();
|
|
150
|
+
}
|
|
193
151
|
|
|
194
|
-
//
|
|
195
|
-
const credential = await result.
|
|
152
|
+
// Or wait for credential
|
|
153
|
+
const credential = await result.credential;
|
|
196
154
|
|
|
197
|
-
//
|
|
198
|
-
|
|
155
|
+
// Cancel if needed
|
|
156
|
+
result.cancel();
|
|
199
157
|
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Smart Defaults & Type Guards (NEW in v4.4+)
|
|
203
|
-
|
|
204
|
-
The SDK now uses **smart defaults** that match the most common use case for each authentication strategy:
|
|
205
158
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
| **TS43** | Headless | `HeadlessResult` with `trigger()` | Browser provides native credential UI |
|
|
210
|
-
| **Desktop** | UI Mode | `Credential` directly | QR modal is complex to build from scratch |
|
|
211
|
-
|
|
212
|
-
#### Using Type Guards
|
|
213
|
-
|
|
214
|
-
Instead of manual type checking, use the SDK's built-in type guards for cleaner, type-safe code:
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
import {
|
|
218
|
-
invokeSecurePrompt,
|
|
219
|
-
isHeadlessResult,
|
|
220
|
-
isLinkStrategy,
|
|
221
|
-
isTS43Strategy,
|
|
222
|
-
isDesktopStrategy
|
|
223
|
-
} from 'glide-web-client-sdk';
|
|
224
|
-
|
|
225
|
-
// No need to specify headless for Link/TS43 - it's the default!
|
|
226
|
-
const result = await invokeSecurePrompt(prepareResult);
|
|
227
|
-
|
|
228
|
-
// Use type guards for type-safe handling
|
|
229
|
-
if (isHeadlessResult(result)) {
|
|
230
|
-
// TypeScript knows this is HeadlessResult
|
|
231
|
-
console.log('Strategy:', result.strategy);
|
|
159
|
+
if (result.strategy === 'link') {
|
|
160
|
+
// Re-trigger app opening
|
|
161
|
+
result.trigger();
|
|
232
162
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await result.pollingPromise;
|
|
237
|
-
}
|
|
238
|
-
else if (isTS43Strategy(result)) {
|
|
239
|
-
// Handle TS43: invoke immediately or on button
|
|
240
|
-
const credential = await result.trigger();
|
|
163
|
+
// Check polling status
|
|
164
|
+
if (result.is_polling) {
|
|
165
|
+
console.log('Currently polling...');
|
|
241
166
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await result.pollingPromise;
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
// Got credential directly (Desktop UI mode by default)
|
|
249
|
-
const processedResult = await verifyPhoneNumberCredential(result, session);
|
|
167
|
+
|
|
168
|
+
// Wait for completion
|
|
169
|
+
const credential = await result.credential;
|
|
250
170
|
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
#### Available Type Guards
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
// Check if result is headless or UI mode
|
|
257
|
-
isHeadlessResult(result) // true if headless mode
|
|
258
|
-
isCredential(result) // true if UI mode (got credential)
|
|
259
|
-
|
|
260
|
-
// Strategy-specific guards (for HeadlessResult)
|
|
261
|
-
isLinkStrategy(result) // true if Link strategy
|
|
262
|
-
isTS43Strategy(result) // true if TS43 strategy
|
|
263
|
-
isDesktopStrategy(result) // true if Desktop strategy
|
|
264
|
-
|
|
265
|
-
// Helper functions
|
|
266
|
-
getStrategy(result) // Returns: 'link' | 'ts43' | 'desktop' | undefined
|
|
267
|
-
requiresPolling(result) // Returns: true for Link/Desktop, false for TS43
|
|
268
|
-
requiresUserAction(result)// Returns: true if user interaction needed
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
#### Override Defaults When Needed
|
|
272
171
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// Force UI mode for Link/TS43 (rarely needed)
|
|
277
|
-
const credential = await invokeSecurePrompt(prepareResult, {
|
|
278
|
-
headless: false // Show SDK modal instead of using custom UI
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Force headless mode for Desktop (for custom QR display)
|
|
282
|
-
const result = await invokeSecurePrompt(prepareResult, {
|
|
283
|
-
headless: true // Get QR data instead of showing modal
|
|
284
|
-
});
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### React Example with Custom UI
|
|
288
|
-
|
|
289
|
-
```jsx
|
|
290
|
-
function CustomPhoneAuth() {
|
|
291
|
-
const [qrCode, setQrCode] = useState(null);
|
|
292
|
-
const [appUrl, setAppUrl] = useState(null);
|
|
172
|
+
if (result.strategy === 'ts43') {
|
|
173
|
+
// Trigger when user is ready
|
|
174
|
+
await result.trigger();
|
|
293
175
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
use_case: 'VerifyPhoneNumber',
|
|
297
|
-
phone_number: phoneNumber
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// No need for headless: true for Link/TS43 - it's the default!
|
|
301
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResult);
|
|
302
|
-
|
|
303
|
-
// Use type guards for clean code
|
|
304
|
-
if (isHeadlessResult(result)) {
|
|
305
|
-
if (isDesktopStrategy(result)) {
|
|
306
|
-
// Desktop in headless (explicitly requested)
|
|
307
|
-
setQrCode(result.qrCode);
|
|
308
|
-
} else if (isLinkStrategy(result)) {
|
|
309
|
-
// Link defaults to headless
|
|
310
|
-
setAppUrl(result.url);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Handle completion
|
|
314
|
-
const credential = await result.pollingPromise;
|
|
315
|
-
console.log('Verified!');
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
return (
|
|
319
|
-
<div className="my-custom-design">
|
|
320
|
-
{qrCode && (
|
|
321
|
-
<div className="qr-container">
|
|
322
|
-
<img src={qrCode} alt="Scan to verify" />
|
|
323
|
-
<p>Scan with your phone</p>
|
|
324
|
-
</div>
|
|
325
|
-
)}
|
|
326
|
-
|
|
327
|
-
{appUrl && (
|
|
328
|
-
<button onClick={() => window.open(appUrl)}>
|
|
329
|
-
Open Verification App
|
|
330
|
-
</button>
|
|
331
|
-
)}
|
|
332
|
-
</div>
|
|
333
|
-
);
|
|
176
|
+
// Get credential
|
|
177
|
+
const credential = await result.credential;
|
|
334
178
|
}
|
|
335
179
|
```
|
|
336
180
|
|
|
337
|
-
###
|
|
338
|
-
|
|
339
|
-
```vue
|
|
340
|
-
<template>
|
|
341
|
-
<div>
|
|
342
|
-
<!-- Use SDK UI for quick setup -->
|
|
343
|
-
<button @click="verifyWithUI">
|
|
344
|
-
Verify (SDK UI)
|
|
345
|
-
</button>
|
|
346
|
-
|
|
347
|
-
<!-- Or build custom UI -->
|
|
348
|
-
<button @click="verifyHeadless">
|
|
349
|
-
Verify (Custom UI)
|
|
350
|
-
</button>
|
|
351
|
-
|
|
352
|
-
<!-- Custom QR display -->
|
|
353
|
-
<div v-if="qrCode" class="custom-qr">
|
|
354
|
-
<img :src="qrCode" />
|
|
355
|
-
</div>
|
|
356
|
-
</div>
|
|
357
|
-
</template>
|
|
358
|
-
|
|
359
|
-
<script setup>
|
|
360
|
-
import { usePhoneAuth } from 'glide-web-client-sdk/vue'
|
|
361
|
-
|
|
362
|
-
const phoneAuth = usePhoneAuth({
|
|
363
|
-
endpoints: {
|
|
364
|
-
prepare: '/api/phone-auth/prepare',
|
|
365
|
-
process: '/api/phone-auth/process'
|
|
366
|
-
}
|
|
367
|
-
});
|
|
181
|
+
### Extended Response Types
|
|
368
182
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
phone_number: '+14155551234'
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// Shows SDK modal
|
|
379
|
-
const credential = await phoneAuth.invokeSecurePrompt(prepared);
|
|
183
|
+
```typescript
|
|
184
|
+
interface ExtendedResponse {
|
|
185
|
+
strategy: 'desktop' | 'link' | 'ts43';
|
|
186
|
+
session: SessionInfo;
|
|
187
|
+
credential: Promise<AuthCredential>;
|
|
188
|
+
cancel: () => void;
|
|
380
189
|
}
|
|
381
190
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const result = await phoneAuth.invokeSecurePrompt(prepared, {
|
|
391
|
-
headless: true
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
if (result.strategy === 'desktop') {
|
|
395
|
-
qrCode.value = result.qrCode;
|
|
396
|
-
await result.pollingPromise;
|
|
397
|
-
}
|
|
191
|
+
interface DesktopExtendedResponse extends ExtendedResponse {
|
|
192
|
+
qr_code_data: {
|
|
193
|
+
ios_qr_image?: string;
|
|
194
|
+
android_qr_image?: string;
|
|
195
|
+
qr_code?: string;
|
|
196
|
+
};
|
|
197
|
+
start_polling?: () => Promise<AuthCredential>;
|
|
198
|
+
is_polling?: boolean;
|
|
398
199
|
}
|
|
399
|
-
</script>
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### TypeScript Types
|
|
403
200
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
headless?: boolean;
|
|
408
|
-
|
|
409
|
-
// Customize built-in UI (only for UI mode)
|
|
410
|
-
modalOptions?: {
|
|
411
|
-
className?: string;
|
|
412
|
-
title?: string;
|
|
413
|
-
description?: string;
|
|
414
|
-
buttonText?: string;
|
|
415
|
-
showCloseButton?: boolean;
|
|
416
|
-
zIndex?: number;
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
// UI event callbacks (only for UI mode)
|
|
420
|
-
callbacks?: {
|
|
421
|
-
onOpen?: () => void;
|
|
422
|
-
onClose?: () => void;
|
|
423
|
-
onAuthStart?: () => void;
|
|
424
|
-
onAuthComplete?: (result: any) => void;
|
|
425
|
-
onError?: (error: Error) => void;
|
|
201
|
+
interface LinkExtendedResponse extends ExtendedResponse {
|
|
202
|
+
data: {
|
|
203
|
+
app_url: string;
|
|
426
204
|
};
|
|
205
|
+
trigger: () => void;
|
|
206
|
+
start_polling?: () => Promise<AuthCredential>;
|
|
207
|
+
is_polling?: boolean;
|
|
427
208
|
}
|
|
428
209
|
|
|
429
|
-
interface
|
|
430
|
-
|
|
431
|
-
url?: string; // For Link strategy
|
|
432
|
-
qrCode?: string; // For Desktop strategy
|
|
433
|
-
pollingPromise?: Promise<any>; // Resolves when complete
|
|
434
|
-
trigger?: () => Promise<any>; // For TS43 strategy
|
|
435
|
-
session: SessionInfo;
|
|
210
|
+
interface TS43ExtendedResponse extends ExtendedResponse {
|
|
211
|
+
trigger: () => Promise<void>;
|
|
436
212
|
}
|
|
437
213
|
```
|
|
438
214
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
### Smart Mode Selection
|
|
215
|
+
### Prevent Default UI
|
|
442
216
|
|
|
443
|
-
|
|
217
|
+
Use `preventDefaultUI: true` to completely disable SDK modals and handle all UI yourself:
|
|
444
218
|
|
|
445
219
|
```javascript
|
|
446
|
-
//
|
|
447
|
-
const result = await phoneAuth.invokeSecurePrompt(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// TS43 Strategy (Android) - Defaults to HEADLESS
|
|
451
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResponse);
|
|
452
|
-
// Automatically headless for custom UI control
|
|
453
|
-
|
|
454
|
-
// Desktop Strategy (QR Code) - Defaults to UI MODE
|
|
455
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResponse);
|
|
456
|
-
// Shows built-in QR modal automatically
|
|
457
|
-
|
|
458
|
-
// Override defaults if needed
|
|
459
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResponse, {
|
|
460
|
-
headless: false // Force UI mode for any strategy
|
|
220
|
+
// Desktop: No QR modal shown
|
|
221
|
+
const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
|
|
222
|
+
executionMode: 'extended',
|
|
223
|
+
preventDefaultUI: true
|
|
461
224
|
});
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### Type Guards for Safe Result Handling
|
|
465
225
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
import {
|
|
470
|
-
isHeadlessResult,
|
|
471
|
-
isCredential,
|
|
472
|
-
isLinkStrategy,
|
|
473
|
-
isTS43Strategy,
|
|
474
|
-
isDesktopStrategy,
|
|
475
|
-
requiresPolling,
|
|
476
|
-
requiresUserAction
|
|
477
|
-
} from 'glide-web-client-sdk';
|
|
478
|
-
|
|
479
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResponse);
|
|
480
|
-
|
|
481
|
-
// Check result type
|
|
482
|
-
if (isHeadlessResult(result)) {
|
|
483
|
-
// TypeScript knows this is HeadlessResult
|
|
484
|
-
console.log('Strategy:', result.strategy);
|
|
226
|
+
// You must display QR yourself
|
|
227
|
+
if (result.strategy === 'desktop') {
|
|
228
|
+
showYourCustomQR(result.qr_code_data);
|
|
485
229
|
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
// Handle Link (App Clip) flow
|
|
489
|
-
await result.trigger(); // Opens App Clip
|
|
490
|
-
const credential = await result.pollingPromise;
|
|
491
|
-
} else if (isTS43Strategy(result)) {
|
|
492
|
-
// Handle TS43 (Android) flow
|
|
493
|
-
const credential = await result.trigger(); // Invokes credential API
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Check capabilities
|
|
497
|
-
if (requiresPolling(result)) {
|
|
498
|
-
// Link or Desktop - needs polling
|
|
499
|
-
const credential = await result.pollingPromise;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (requiresUserAction(result)) {
|
|
503
|
-
// Link or TS43 - needs user interaction
|
|
504
|
-
button.onclick = () => result.trigger();
|
|
505
|
-
}
|
|
506
|
-
} else if (isCredential(result)) {
|
|
507
|
-
// Direct credential (UI mode handled everything)
|
|
508
|
-
const verified = await phoneAuth.processCredential(result);
|
|
230
|
+
// Start polling manually (required when preventDefaultUI is true)
|
|
231
|
+
const credential = await result.start_polling();
|
|
509
232
|
}
|
|
510
233
|
```
|
|
511
234
|
|
|
512
|
-
###
|
|
235
|
+
### Parent Session Support
|
|
513
236
|
|
|
514
|
-
|
|
237
|
+
For continuing authentication from another device:
|
|
515
238
|
|
|
516
239
|
```javascript
|
|
517
|
-
//
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
});
|
|
521
|
-
button.onclick = () => linkResult.trigger(); // User controls when to open
|
|
522
|
-
|
|
523
|
-
// TS43 Strategy - trigger invokes credential API
|
|
524
|
-
const ts43Result = await phoneAuth.invokeSecurePrompt(prepareResponse, {
|
|
525
|
-
autoTrigger: false // Preserve user gesture
|
|
240
|
+
// Desktop starts authentication
|
|
241
|
+
const desktopResult = await phoneAuth.preparePhoneRequest({
|
|
242
|
+
use_case: 'GetPhoneNumber'
|
|
526
243
|
});
|
|
527
|
-
button.onclick = async () => {
|
|
528
|
-
const credential = await ts43Result.trigger(); // Must be in click handler
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// Auto-trigger (default for Link)
|
|
532
|
-
const autoResult = await phoneAuth.invokeSecurePrompt(prepareResponse);
|
|
533
|
-
// App Clip opens immediately, retry available
|
|
534
|
-
retryButton.onclick = () => autoResult.trigger(); // Retry if needed
|
|
535
|
-
```
|
|
536
244
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
### How It Works
|
|
542
|
-
|
|
543
|
-
1. **Detection**: After 3 seconds with credential prompt open, the SDK detects likely QR code flow
|
|
544
|
-
2. **Timeout Extension**: Automatically extends timeout from 30 seconds to 2 minutes
|
|
545
|
-
3. **User Experience**: Users have ample time to scan QR and complete on their phone
|
|
546
|
-
|
|
547
|
-
### Monitoring Cross-Device Flows
|
|
548
|
-
|
|
549
|
-
```javascript
|
|
550
|
-
const { getPhoneNumber } = usePhoneAuth({
|
|
551
|
-
endpoints: { /* ... */ },
|
|
552
|
-
|
|
553
|
-
// Optional: Track when QR codes are shown
|
|
554
|
-
onCrossDeviceDetected: () => {
|
|
555
|
-
// Analytics: Track QR code usage
|
|
556
|
-
analytics.track('phone_auth_qr_code_shown');
|
|
557
|
-
}
|
|
245
|
+
// Mobile continues with parent session
|
|
246
|
+
const mobileResult = await phoneAuth.preparePhoneRequest({
|
|
247
|
+
parent_session_id: desktopResult.session.session_id
|
|
248
|
+
// use_case inherited from parent
|
|
558
249
|
});
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Silent Retry Pattern
|
|
562
|
-
|
|
563
|
-
The SDK automatically retries transient failures without showing errors to users.
|
|
564
|
-
|
|
565
|
-
### Default Behavior
|
|
566
|
-
|
|
567
|
-
- **2 automatic retries** on network/timeout errors
|
|
568
|
-
- **Exponential backoff** between attempts (1s, 2s)
|
|
569
|
-
- **Error only shown** after all retries are exhausted
|
|
570
|
-
- **Session caching** to recover from interruptions
|
|
571
|
-
|
|
572
|
-
### Example Flow
|
|
573
250
|
|
|
251
|
+
// Complete authentication on mobile
|
|
252
|
+
const credential = await phoneAuth.invokeSecurePrompt(mobileResult);
|
|
574
253
|
```
|
|
575
|
-
User clicks verify →
|
|
576
|
-
Try 1 fails (network glitch) →
|
|
577
|
-
[Silent retry - no error shown] →
|
|
578
|
-
Try 2 succeeds →
|
|
579
|
-
✓ Success shown to user
|
|
580
254
|
|
|
581
|
-
|
|
582
|
-
```
|
|
255
|
+
## Authentication Strategies
|
|
583
256
|
|
|
584
|
-
|
|
257
|
+
The SDK automatically selects the appropriate authentication strategy based on the device and browser:
|
|
585
258
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
onRetryAttempt: (attempt, maxAttempts) => {
|
|
592
|
-
// Log to monitoring service
|
|
593
|
-
logger.info('Phone auth retry', {
|
|
594
|
-
attempt,
|
|
595
|
-
maxAttempts
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
## Advanced Features
|
|
602
|
-
|
|
603
|
-
### Manual Retry
|
|
604
|
-
|
|
605
|
-
Users can manually retry failed verifications:
|
|
606
|
-
|
|
607
|
-
```javascript
|
|
608
|
-
const { retryLastRequest, error } = usePhoneAuth(config);
|
|
609
|
-
|
|
610
|
-
// Show retry button after error
|
|
611
|
-
if (error) {
|
|
612
|
-
<button onClick={retryLastRequest}>Try Again</button>
|
|
613
|
-
}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### Callbacks
|
|
259
|
+
### Desktop Strategy (QR Code)
|
|
260
|
+
- **When**: Desktop browsers
|
|
261
|
+
- **How**: Displays QR code for mobile scanning
|
|
262
|
+
- **Default**: Shows built-in QR modal
|
|
263
|
+
- **Custom**: Use `preventDefaultUI: true` to handle QR display yourself
|
|
617
264
|
|
|
618
|
-
|
|
265
|
+
### Link Strategy (App Clips/Deep Links)
|
|
266
|
+
- **When**: Mobile browsers on iOS/Android
|
|
267
|
+
- **How**: Opens native app or App Clip
|
|
268
|
+
- **Default**: Opens app link immediately
|
|
269
|
+
- **Custom**: Control when to open with `trigger()` in extended mode
|
|
619
270
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
271
|
+
### TS43 Strategy (Digital Credentials)
|
|
272
|
+
- **When**: Chrome/Edge on Android with Digital Credentials API
|
|
273
|
+
- **How**: Uses browser's native credential prompt
|
|
274
|
+
- **Default**: Shows credential prompt immediately
|
|
275
|
+
- **Custom**: Control timing with `trigger()` in extended mode
|
|
624
276
|
|
|
625
|
-
|
|
277
|
+
## Type Guards for Safe Handling
|
|
626
278
|
|
|
627
|
-
|
|
279
|
+
Use built-in type guards for type-safe result handling:
|
|
628
280
|
|
|
629
281
|
```typescript
|
|
630
282
|
import {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
} from '
|
|
636
|
-
|
|
637
|
-
const config: UsePhoneAuthOptions = {
|
|
638
|
-
endpoints: {
|
|
639
|
-
prepare: '/api/prepare',
|
|
640
|
-
process: '/api/process'
|
|
641
|
-
},
|
|
642
|
-
onCrossDeviceDetected: () => void,
|
|
643
|
-
onRetryAttempt: (attempt: number, max: number) => void
|
|
644
|
-
};
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
## Error Handling
|
|
648
|
-
|
|
649
|
-
### Enhanced Error Context
|
|
650
|
-
|
|
651
|
-
Errors now include additional context for debugging:
|
|
652
|
-
|
|
653
|
-
```javascript
|
|
654
|
-
catch (error) {
|
|
655
|
-
console.log(error.code); // 'REQUEST_TIMEOUT'
|
|
656
|
-
console.log(error.message); // User-friendly message
|
|
657
|
-
console.log(error.context); // { attemptNumber: 3, maxAttempts: 3 }
|
|
658
|
-
console.log(error.browserError); // Browser-specific error details
|
|
659
|
-
}
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
### Cross-Device Error Types
|
|
283
|
+
isExtendedResponse,
|
|
284
|
+
isDesktopStrategy,
|
|
285
|
+
isLinkStrategy,
|
|
286
|
+
isTS43Strategy
|
|
287
|
+
} from '@glideidentity/web-client-sdk';
|
|
663
288
|
|
|
664
|
-
|
|
289
|
+
const result = await phoneAuth.invokeSecurePrompt(prepareResponse, {
|
|
290
|
+
executionMode: 'extended'
|
|
291
|
+
});
|
|
665
292
|
|
|
666
|
-
|
|
667
|
-
if (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
293
|
+
// Check if extended response
|
|
294
|
+
if (isExtendedResponse(result)) {
|
|
295
|
+
console.log('Strategy:', result.strategy);
|
|
296
|
+
|
|
297
|
+
// Check specific strategies
|
|
298
|
+
if (isDesktopStrategy(result)) {
|
|
299
|
+
// TypeScript knows this is DesktopExtendedResponse
|
|
300
|
+
console.log('QR data:', result.qr_code_data);
|
|
301
|
+
} else if (isLinkStrategy(result)) {
|
|
302
|
+
// TypeScript knows this is LinkExtendedResponse
|
|
303
|
+
console.log('App URL:', result.data.app_url);
|
|
304
|
+
} else if (isTS43Strategy(result)) {
|
|
305
|
+
// TypeScript knows this is TS43ExtendedResponse
|
|
306
|
+
await result.trigger();
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Standard mode - got credential directly
|
|
310
|
+
const verified = await phoneAuth.verifyPhoneNumberCredential(result, session);
|
|
672
311
|
}
|
|
673
312
|
```
|
|
674
313
|
|
|
675
|
-
##
|
|
676
|
-
|
|
677
|
-
### Requirements
|
|
314
|
+
## Framework Examples
|
|
678
315
|
|
|
679
|
-
|
|
680
|
-
- Digital Credentials API flag enabled
|
|
681
|
-
|
|
682
|
-
### Checking Support
|
|
683
|
-
|
|
684
|
-
```javascript
|
|
685
|
-
const { isSupported, browserSupportInfo } = usePhoneAuth();
|
|
686
|
-
|
|
687
|
-
if (!isSupported) {
|
|
688
|
-
console.log(browserSupportInfo.message);
|
|
689
|
-
// "Please enable chrome://flags/#web-identity-digital-credentials"
|
|
690
|
-
}
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
## Vue 3 Example
|
|
316
|
+
### Vue 3 Example
|
|
694
317
|
|
|
695
318
|
```vue
|
|
696
319
|
<template>
|
|
697
320
|
<div>
|
|
698
|
-
<button @click="
|
|
321
|
+
<button @click="verifyPhone" :disabled="isLoading">
|
|
699
322
|
Verify Phone
|
|
700
323
|
</button>
|
|
701
324
|
|
|
702
|
-
<!-- Retry button -->
|
|
703
|
-
<button v-if="error" @click="retryLastRequest">
|
|
704
|
-
Retry
|
|
705
|
-
</button>
|
|
706
|
-
|
|
707
325
|
<div v-if="result">
|
|
708
326
|
Phone: {{ result.phone_number }}
|
|
709
327
|
</div>
|
|
@@ -711,201 +329,274 @@ if (!isSupported) {
|
|
|
711
329
|
</template>
|
|
712
330
|
|
|
713
331
|
<script setup>
|
|
714
|
-
import {
|
|
715
|
-
|
|
716
|
-
const {
|
|
717
|
-
verifyPhoneNumber,
|
|
718
|
-
retryLastRequest,
|
|
719
|
-
isLoading,
|
|
720
|
-
error,
|
|
721
|
-
result
|
|
722
|
-
} = usePhoneAuth({
|
|
723
|
-
endpoints: {
|
|
724
|
-
prepare: '/api/phone-auth/prepare',
|
|
725
|
-
process: '/api/phone-auth/process'
|
|
726
|
-
},
|
|
727
|
-
onCrossDeviceDetected: () => {
|
|
728
|
-
console.log('QR code shown');
|
|
729
|
-
}
|
|
730
|
-
})
|
|
332
|
+
import { ref } from 'vue';
|
|
333
|
+
import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
|
|
731
334
|
|
|
732
|
-
const
|
|
733
|
-
|
|
335
|
+
const phoneAuth = new PhoneAuthClient({
|
|
336
|
+
maxPollingAttempts: 30
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const isLoading = ref(false);
|
|
340
|
+
const result = ref(null);
|
|
341
|
+
|
|
342
|
+
async function verifyPhone() {
|
|
343
|
+
isLoading.value = true;
|
|
344
|
+
try {
|
|
345
|
+
const prepared = await phoneAuth.preparePhoneRequest({
|
|
346
|
+
use_case: 'VerifyPhoneNumber',
|
|
347
|
+
phone_number: '+14155551234'
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const credential = await phoneAuth.invokeSecurePrompt(prepared);
|
|
351
|
+
result.value = await phoneAuth.verifyPhoneNumberCredential(
|
|
352
|
+
credential,
|
|
353
|
+
prepared.session
|
|
354
|
+
);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error('Failed:', error);
|
|
357
|
+
} finally {
|
|
358
|
+
isLoading.value = false;
|
|
359
|
+
}
|
|
734
360
|
}
|
|
735
361
|
</script>
|
|
736
362
|
```
|
|
737
363
|
|
|
738
|
-
|
|
364
|
+
### Angular Example
|
|
739
365
|
|
|
740
366
|
```typescript
|
|
741
367
|
import { Component } from '@angular/core';
|
|
742
|
-
import {
|
|
368
|
+
import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
|
|
743
369
|
|
|
744
370
|
@Component({
|
|
745
371
|
selector: 'app-phone-verify',
|
|
746
372
|
template: `
|
|
747
|
-
<button (click)="verify()" [disabled]="isLoading
|
|
373
|
+
<button (click)="verify()" [disabled]="isLoading">
|
|
748
374
|
Verify
|
|
749
375
|
</button>
|
|
750
376
|
|
|
751
|
-
<
|
|
752
|
-
Retry
|
|
753
|
-
</button>
|
|
754
|
-
|
|
755
|
-
<div *ngIf="result$ | async as result">
|
|
377
|
+
<div *ngIf="result">
|
|
756
378
|
Phone: {{ result.phone_number }}
|
|
757
379
|
</div>
|
|
758
380
|
`
|
|
759
381
|
})
|
|
760
382
|
export class PhoneVerifyComponent {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
383
|
+
phoneAuth = new PhoneAuthClient({
|
|
384
|
+
// Optional configuration
|
|
385
|
+
debug: false
|
|
386
|
+
});
|
|
764
387
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
endpoints: {
|
|
768
|
-
prepare: '/api/phone-auth/prepare',
|
|
769
|
-
process: '/api/phone-auth/process'
|
|
770
|
-
},
|
|
771
|
-
onCrossDeviceDetected: () => {
|
|
772
|
-
console.log('QR code detected');
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
}
|
|
388
|
+
isLoading = false;
|
|
389
|
+
result: any = null;
|
|
776
390
|
|
|
777
391
|
async verify() {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
392
|
+
this.isLoading = true;
|
|
393
|
+
try {
|
|
394
|
+
const prepared = await this.phoneAuth.preparePhoneRequest({
|
|
395
|
+
use_case: 'VerifyPhoneNumber',
|
|
396
|
+
phone_number: '+14155551234'
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const credential = await this.phoneAuth.invokeSecurePrompt(prepared);
|
|
400
|
+
this.result = await this.phoneAuth.verifyPhoneNumberCredential(
|
|
401
|
+
credential,
|
|
402
|
+
prepared.session
|
|
403
|
+
);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('Failed:', error);
|
|
406
|
+
} finally {
|
|
407
|
+
this.isLoading = false;
|
|
408
|
+
}
|
|
783
409
|
}
|
|
784
410
|
}
|
|
785
411
|
```
|
|
786
412
|
|
|
787
|
-
|
|
413
|
+
### Vanilla JavaScript
|
|
788
414
|
|
|
789
415
|
```javascript
|
|
790
|
-
import { PhoneAuthClient } from '
|
|
416
|
+
import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
|
|
791
417
|
|
|
792
418
|
const client = new PhoneAuthClient({
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
},
|
|
797
|
-
onCrossDeviceDetected: () => {
|
|
798
|
-
console.log('QR code detected');
|
|
799
|
-
},
|
|
800
|
-
onRetryAttempt: (attempt, max) => {
|
|
801
|
-
console.log(`Retry ${attempt}/${max}`);
|
|
802
|
-
}
|
|
419
|
+
pollingInterval: 2000,
|
|
420
|
+
maxPollingAttempts: 30,
|
|
421
|
+
debug: false
|
|
803
422
|
});
|
|
804
423
|
|
|
805
|
-
//
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
424
|
+
// Get phone number
|
|
425
|
+
async function getPhoneNumber() {
|
|
426
|
+
try {
|
|
427
|
+
const prepareResult = await client.preparePhoneRequest({
|
|
428
|
+
use_case: 'GetPhoneNumber'
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const credential = await client.invokeSecurePrompt(prepareResult);
|
|
432
|
+
|
|
433
|
+
const result = await client.getPhoneNumberCredential(
|
|
434
|
+
credential,
|
|
435
|
+
prepareResult.session
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
console.log('Phone:', result.phone_number);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.error('Failed:', error);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Verify with extended mode
|
|
445
|
+
async function verifyWithExtendedMode() {
|
|
446
|
+
try {
|
|
447
|
+
const prepareResult = await client.preparePhoneRequest({
|
|
448
|
+
use_case: 'VerifyPhoneNumber',
|
|
449
|
+
phone_number: '+14155551234'
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const extendedResult = await client.invokeSecurePrompt(prepareResult, {
|
|
453
|
+
executionMode: 'extended',
|
|
454
|
+
preventDefaultUI: true
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Handle based on strategy
|
|
458
|
+
if (extendedResult.strategy === 'desktop') {
|
|
459
|
+
// Show custom QR
|
|
460
|
+
document.getElementById('qr').src = extendedResult.qr_code_data.qr_code;
|
|
461
|
+
|
|
462
|
+
// Start polling
|
|
463
|
+
const credential = await extendedResult.start_polling();
|
|
464
|
+
console.log('Verified!');
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error('Failed:', error);
|
|
468
|
+
}
|
|
812
469
|
}
|
|
813
470
|
```
|
|
814
471
|
|
|
815
|
-
##
|
|
472
|
+
## Error Handling
|
|
816
473
|
|
|
817
|
-
|
|
474
|
+
The SDK provides detailed error information:
|
|
818
475
|
|
|
819
476
|
```javascript
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
//
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
477
|
+
try {
|
|
478
|
+
const result = await phoneAuth.invokeSecurePrompt(prepareResponse);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.log(error.code); // e.g., 'CARRIER_NOT_ELIGIBLE'
|
|
481
|
+
console.log(error.message); // User-friendly error message
|
|
482
|
+
|
|
483
|
+
switch (error.code) {
|
|
484
|
+
case 'CARRIER_NOT_ELIGIBLE':
|
|
485
|
+
// Handle unsupported carrier
|
|
486
|
+
break;
|
|
487
|
+
case 'USER_DENIED':
|
|
488
|
+
// User cancelled authentication
|
|
489
|
+
break;
|
|
490
|
+
case 'TIMEOUT':
|
|
491
|
+
// Authentication timed out
|
|
492
|
+
break;
|
|
493
|
+
default:
|
|
494
|
+
// Handle other errors
|
|
495
|
+
}
|
|
496
|
+
}
|
|
829
497
|
```
|
|
830
498
|
|
|
831
|
-
##
|
|
499
|
+
## Browser Support
|
|
832
500
|
|
|
833
|
-
|
|
501
|
+
### Requirements
|
|
502
|
+
|
|
503
|
+
- Chrome 118+ or Edge 118+ (for TS43 strategy)
|
|
504
|
+
- Digital Credentials API flag enabled (for TS43)
|
|
505
|
+
- Any modern browser for Desktop/Link strategies
|
|
834
506
|
|
|
835
|
-
|
|
836
|
-
- `AppEnhanced.jsx` - Production app with debug mode
|
|
507
|
+
### Checking Support
|
|
837
508
|
|
|
838
|
-
|
|
509
|
+
```javascript
|
|
510
|
+
// Check if current browser/device is supported
|
|
511
|
+
const isSupported = phoneAuth.isSupported();
|
|
512
|
+
|
|
513
|
+
if (!isSupported) {
|
|
514
|
+
console.log('Phone authentication not supported on this device');
|
|
515
|
+
}
|
|
516
|
+
```
|
|
839
517
|
|
|
840
518
|
## API Reference
|
|
841
519
|
|
|
842
520
|
### Configuration Options
|
|
843
521
|
|
|
844
522
|
```typescript
|
|
845
|
-
interface
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
523
|
+
interface PhoneAuthConfig {
|
|
524
|
+
endpoints?: {
|
|
525
|
+
prepare?: string; // Custom prepare endpoint
|
|
526
|
+
process?: string; // Custom process endpoint
|
|
527
|
+
polling?: string; // Custom polling endpoint
|
|
528
|
+
};
|
|
529
|
+
pollingInterval?: number; // Polling interval in ms (default: 2000)
|
|
530
|
+
maxPollingAttempts?: number; // Max polling attempts (default: 30 = 1 minute)
|
|
531
|
+
timeout?: number; // API timeout in ms (default: 30000)
|
|
532
|
+
debug?: boolean; // Enable debug logging (default: false)
|
|
533
|
+
devtools?: {
|
|
534
|
+
showMobileConsole?: boolean; // Show debug console on mobile (default: false)
|
|
851
535
|
};
|
|
852
|
-
|
|
853
|
-
// Optional settings
|
|
854
|
-
pollingInterval?: number; // Polling interval in ms (default: 2000)
|
|
855
|
-
maxPollingAttempts?: number; // Max polling attempts (default: 150)
|
|
856
|
-
timeout?: number; // API timeout in ms (default: 30000)
|
|
857
|
-
debug?: boolean; // Enable debug logging (default: false)
|
|
858
|
-
|
|
859
|
-
// Optional callbacks
|
|
860
|
-
onCrossDeviceDetected?: () => void; // QR code shown
|
|
861
|
-
onRetryAttempt?: (attempt: number, maxAttempts: number) => void;
|
|
862
|
-
onTimeout?: () => void;
|
|
863
|
-
onCancel?: () => void;
|
|
864
536
|
}
|
|
865
537
|
```
|
|
866
538
|
|
|
867
|
-
###
|
|
539
|
+
### Invoke Options
|
|
868
540
|
|
|
869
541
|
```typescript
|
|
870
|
-
interface
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
542
|
+
interface InvokeOptions {
|
|
543
|
+
// Execution mode
|
|
544
|
+
executionMode?: 'standard' | 'extended'; // Default: 'standard'
|
|
545
|
+
preventDefaultUI?: boolean; // Default: false
|
|
546
|
+
|
|
547
|
+
// UI customization (when preventDefaultUI is false)
|
|
548
|
+
modalOptions?: {
|
|
549
|
+
className?: string;
|
|
550
|
+
title?: string;
|
|
551
|
+
description?: string;
|
|
552
|
+
buttonText?: string;
|
|
553
|
+
showCloseButton?: boolean;
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// Callbacks
|
|
557
|
+
callbacks?: {
|
|
558
|
+
onOpen?: () => void;
|
|
559
|
+
onClose?: () => void;
|
|
560
|
+
onAuthStart?: () => void;
|
|
561
|
+
onAuthComplete?: (result: any) => void;
|
|
562
|
+
onError?: (error: Error) => void;
|
|
563
|
+
};
|
|
876
564
|
}
|
|
877
|
-
|
|
878
|
-
// Usage
|
|
879
|
-
const result = await phoneAuth.invokeSecurePrompt(prepareResponse, {
|
|
880
|
-
headless: false,
|
|
881
|
-
modalOptions: {
|
|
882
|
-
title: 'Verify Your Identity',
|
|
883
|
-
buttonText: 'Continue'
|
|
884
|
-
}
|
|
885
|
-
});
|
|
886
565
|
```
|
|
887
566
|
|
|
888
567
|
### Methods
|
|
889
568
|
|
|
890
569
|
```typescript
|
|
891
|
-
//
|
|
892
|
-
getPhoneNumber(): Promise<GetPhoneNumberResponse>
|
|
893
|
-
verifyPhoneNumber(phoneNumber?: string): Promise<VerifyPhoneNumberResponse>
|
|
894
|
-
|
|
895
|
-
// Low-level methods (granular control)
|
|
570
|
+
// Core methods
|
|
896
571
|
preparePhoneRequest(options: PrepareRequest): Promise<PrepareResponse>
|
|
897
|
-
invokeSecurePrompt(response: PrepareResponse, options?: InvokeOptions): Promise<
|
|
572
|
+
invokeSecurePrompt(response: PrepareResponse, options?: InvokeOptions): Promise<any>
|
|
898
573
|
getPhoneNumberCredential(credential: any, session: SessionInfo): Promise<GetPhoneNumberResponse>
|
|
899
574
|
verifyPhoneNumberCredential(credential: any, session: SessionInfo): Promise<VerifyPhoneNumberResponse>
|
|
900
575
|
|
|
576
|
+
// High-level convenience methods
|
|
577
|
+
getPhoneNumberComplete(): Promise<GetPhoneNumberResponse>
|
|
578
|
+
verifyPhoneNumberComplete(phoneNumber: string): Promise<VerifyPhoneNumberResponse>
|
|
579
|
+
|
|
901
580
|
// Utility methods
|
|
902
|
-
|
|
581
|
+
isSupported(): boolean
|
|
903
582
|
cancelCurrentRequest(): void
|
|
904
583
|
```
|
|
905
584
|
|
|
906
585
|
### Response Types
|
|
907
586
|
|
|
908
587
|
```typescript
|
|
588
|
+
interface PrepareRequest {
|
|
589
|
+
use_case: 'GetPhoneNumber' | 'VerifyPhoneNumber';
|
|
590
|
+
phone_number?: string;
|
|
591
|
+
parent_session_id?: string;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
interface PrepareResponse {
|
|
595
|
+
authentication_strategy: 'desktop' | 'link' | 'ts43';
|
|
596
|
+
session: SessionInfo;
|
|
597
|
+
data: any;
|
|
598
|
+
}
|
|
599
|
+
|
|
909
600
|
interface GetPhoneNumberResponse {
|
|
910
601
|
phone_number: string;
|
|
911
602
|
aud?: string;
|
|
@@ -916,23 +607,13 @@ interface VerifyPhoneNumberResponse {
|
|
|
916
607
|
verified: boolean;
|
|
917
608
|
aud?: string;
|
|
918
609
|
}
|
|
919
|
-
|
|
920
|
-
interface HeadlessResult {
|
|
921
|
-
strategy: 'link' | 'ts43' | 'desktop';
|
|
922
|
-
trigger?: () => void | Promise<any>;
|
|
923
|
-
pollingPromise?: Promise<any>;
|
|
924
|
-
session: SessionInfo;
|
|
925
|
-
url?: string; // For Link strategy
|
|
926
|
-
qrCode?: string; // For Desktop strategy
|
|
927
|
-
}
|
|
928
610
|
```
|
|
929
611
|
|
|
930
612
|
## Support
|
|
931
613
|
|
|
932
614
|
- [Documentation](https://docs.glideidentity.com)
|
|
933
|
-
- [API Reference](https://
|
|
934
|
-
- [Support](https://support.glideidentity.com)
|
|
615
|
+
- [API Reference](https://docs.glideidentity.com/api-reference)
|
|
935
616
|
|
|
936
617
|
## License
|
|
937
618
|
|
|
938
|
-
MIT
|
|
619
|
+
MIT
|