@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 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
- - 📱 **Cross-Device Support**: Desktop QR code, mobile App Clips, and Android deep links
11
- - 🤖 **Smart Defaults**: Automatic headless/UI mode selection based on strategy
12
- - 🔄 **Unified Trigger Pattern**: Consistent API for Link and TS43 authentication
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
- ### 🎉 Smart Defaults & Better DX
20
- - **Smart Mode Selection**: Link/TS43 default to headless, Desktop defaults to UI mode
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
- - **Cleaner API**: Removed non-functional options (iframe, redundant callbacks)
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
- - [Smart Defaults & Type Guards](#smart-defaults--type-guards)
32
- - [Cross-Device Support](#cross-device-support)
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 glide-web-client-sdk
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 { usePhoneAuth } from 'glide-web-client-sdk/react'
51
+ import React, { useState } from 'react';
52
+ import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
51
53
 
52
54
  function PhoneVerification() {
53
- const {
54
- getPhoneNumber,
55
- verifyPhoneNumber,
56
- retryLastRequest, // NEW: Manual retry capability
57
- isLoading,
58
- error,
59
- result,
60
- currentStep,
61
- isSupported
62
- } = usePhoneAuth({
63
- endpoints: {
64
- prepare: '/api/phone-auth/prepare',
65
- process: '/api/phone-auth/process'
66
- },
67
- // Optional callbacks for monitoring
68
- onCrossDeviceDetected: () => {
69
- console.log('QR code detected - user completing on phone');
70
- },
71
- onRetryAttempt: (attempt, maxAttempts) => {
72
- console.log(`Silent retry ${attempt}/${maxAttempts}`);
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 handleVerify = async () => {
83
+ const handleVerifyPhoneNumber = async () => {
77
84
  try {
78
- // Verify a specific phone number
79
- await verifyPhoneNumber('+1234567890')
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
- // Error only thrown after all retries exhausted
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={handleVerify} disabled={isLoading}>
89
- Verify My Phone
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
- ## 🎨 Dual-Mode API: UI Mode vs Headless Mode
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
- ### **Headless Mode** - Complete Control
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 Each Mode
123
+ ### When to Use Extended Mode
116
124
 
117
- | Use Case | Mode | Why |
118
- |----------|------|-----|
119
- | Quick integration | UI Mode | Get running in minutes with pre-built components |
120
- | Custom design requirements | Headless | Match your exact brand guidelines |
121
- | Mobile apps (React Native, etc) | Headless | Native UI components |
122
- | Testing/Automation | Headless | Direct control over flow |
123
- | Standard web apps | UI Mode | Optimized UX out of the box |
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
- ### UI Mode Example (Default)
133
+ ### Extended Mode Example
126
134
 
127
135
  ```javascript
128
- // Shows SDK's built-in UI based on authentication strategy
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
- headless: true
138
+ executionMode: 'extended',
139
+ preventDefaultUI: true // Optional: disable all SDK UI
167
140
  });
168
141
 
169
- // Handle each strategy with your custom UI
170
- if (result.strategy === 'link') {
171
- // iOS/Android - App Clip or native app
172
- console.log('URL to open:', result.url);
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
- // Wait for completion
178
- const credential = await result.pollingPromise;
179
-
180
- } else if (result.strategy === 'desktop') {
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
- // Trigger when user clicks your custom button
195
- const credential = await result.trigger();
152
+ // Or wait for credential
153
+ const credential = await result.credential;
196
154
 
197
- // Note: TS43 never shows a modal in UI mode - the OS provides its own UI
198
- // The Digital Credentials API shows a native drawer/bottom sheet
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
- | Strategy | Default Mode | Returns | Why |
207
- |----------|-------------|---------|-----|
208
- | **Link** | Headless | `HeadlessResult` with `trigger()` | Developers want custom "Open App" buttons |
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
- if (isLinkStrategy(result)) {
234
- // Handle Link: custom button to open app
235
- myButton.onclick = () => result.trigger();
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
- else if (isDesktopStrategy(result)) {
243
- // Desktop in headless mode (explicitly requested)
244
- showCustomQR(result.qrCodeUrl);
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
- You can still override the defaults when you have specific requirements:
274
-
275
- ```javascript
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
- const handleVerify = async () => {
295
- const prepareResult = await phoneAuth.preparePhoneRequest({
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
- ### Vue Example with Both Modes
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
- const qrCode = ref(null);
370
-
371
- // Using built-in UI
372
- async function verifyWithUI() {
373
- const prepared = await phoneAuth.preparePhoneRequest({
374
- use_case: 'VerifyPhoneNumber',
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
- // Using headless mode
383
- async function verifyHeadless() {
384
- const prepared = await phoneAuth.preparePhoneRequest({
385
- use_case: 'VerifyPhoneNumber',
386
- phone_number: '+14155551234'
387
- });
388
-
389
- // Get raw data
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
- ```typescript
405
- interface InvokeOptions {
406
- // Enable headless mode (no UI)
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 HeadlessResult {
430
- strategy: 'ts43' | 'link' | 'desktop';
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
- ## Smart Defaults & Type Guards
440
-
441
- ### Smart Mode Selection
215
+ ### Prevent Default UI
442
216
 
443
- The SDK automatically chooses the optimal mode based on the authentication strategy:
217
+ Use `preventDefaultUI: true` to completely disable SDK modals and handle all UI yourself:
444
218
 
445
219
  ```javascript
446
- // Link Strategy (App Clips) - Defaults to HEADLESS
447
- const result = await phoneAuth.invokeSecurePrompt(prepareResponse);
448
- // No need to specify headless: true - it's automatic!
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
- Use built-in type guards for type-safe result handling:
467
-
468
- ```typescript
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
- // Check specific strategies
487
- if (isLinkStrategy(result)) {
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
- ### Unified Trigger Pattern
235
+ ### Parent Session Support
513
236
 
514
- All headless strategies use the same `trigger()` pattern:
237
+ For continuing authentication from another device:
515
238
 
516
239
  ```javascript
517
- // Link Strategy - trigger opens App Clip
518
- const linkResult = await phoneAuth.invokeSecurePrompt(prepareResponse, {
519
- autoTrigger: false // Don't auto-open, let user click
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
- ## Cross-Device Support
538
-
539
- The SDK automatically handles cross-device authentication flows (QR code scenarios) without any configuration needed.
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
- User never sees the transient failure!
582
- ```
255
+ ## Authentication Strategies
583
256
 
584
- ### Monitoring Retries
257
+ The SDK automatically selects the appropriate authentication strategy based on the device and browser:
585
258
 
586
- ```javascript
587
- const { verifyPhoneNumber } = usePhoneAuth({
588
- endpoints: { /* ... */ },
589
-
590
- // Optional: Track retry patterns
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
- Both callbacks are optional and have no performance impact when not used:
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
- | Callback | Purpose | Use Case |
621
- |----------|---------|----------|
622
- | `onCrossDeviceDetected` | Fires when QR code flow detected | Analytics, custom UI |
623
- | `onRetryAttempt` | Fires on each retry attempt | Monitoring, debugging |
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
- ### TypeScript Support
277
+ ## Type Guards for Safe Handling
626
278
 
627
- Full TypeScript support with exported types:
279
+ Use built-in type guards for type-safe result handling:
628
280
 
629
281
  ```typescript
630
282
  import {
631
- usePhoneAuth,
632
- PhoneAuthResult,
633
- AuthError,
634
- UsePhoneAuthOptions
635
- } from 'glide-web-client-sdk/react';
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
- The SDK provides specific error information for cross-device issues:
289
+ const result = await phoneAuth.invokeSecurePrompt(prepareResponse, {
290
+ executionMode: 'extended'
291
+ });
665
292
 
666
- ```javascript
667
- if (error.details?.errorType === 'CROSS_DEVICE_TIMEOUT') {
668
- // QR code expired
669
- }
670
- if (error.details?.errorType === 'CROSS_DEVICE_CONNECTION_LOST') {
671
- // Connection lost during phone verification
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
- ## Browser Support
676
-
677
- ### Requirements
314
+ ## Framework Examples
678
315
 
679
- - Chrome 118+ or Edge 118+
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="verify" :disabled="isLoading">
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 { usePhoneAuth } from 'glide-web-client-sdk/vue'
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 verify = async () => {
733
- await verifyPhoneNumber('+1234567890')
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
- ## Angular Example
364
+ ### Angular Example
739
365
 
740
366
  ```typescript
741
367
  import { Component } from '@angular/core';
742
- import { PhoneAuthService } from 'glide-web-client-sdk/angular';
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$ | async">
373
+ <button (click)="verify()" [disabled]="isLoading">
748
374
  Verify
749
375
  </button>
750
376
 
751
- <button *ngIf="error$ | async" (click)="retry()">
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
- isLoading$ = this.phoneAuth.isLoading$;
762
- error$ = this.phoneAuth.error$;
763
- result$ = this.phoneAuth.result$;
383
+ phoneAuth = new PhoneAuthClient({
384
+ // Optional configuration
385
+ debug: false
386
+ });
764
387
 
765
- constructor(private phoneAuth: PhoneAuthService) {
766
- this.phoneAuth.configure({
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
- await this.phoneAuth.verifyPhoneNumber('+1234567890');
779
- }
780
-
781
- async retry() {
782
- await this.phoneAuth.retryLastRequest();
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
- ## Vanilla JavaScript
413
+ ### Vanilla JavaScript
788
414
 
789
415
  ```javascript
790
- import { PhoneAuthClient } from 'glide-web-client-sdk';
416
+ import { PhoneAuthClient } from '@glideidentity/web-client-sdk';
791
417
 
792
418
  const client = new PhoneAuthClient({
793
- endpoints: {
794
- prepare: '/api/phone-auth/prepare',
795
- process: '/api/phone-auth/process'
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
- // Verify phone number with automatic retry
806
- try {
807
- const result = await client.verifyPhoneNumber('+1234567890');
808
- console.log('Verified:', result.verified);
809
- } catch (error) {
810
- // Only thrown after all retries exhausted
811
- console.error('Failed after retries:', error);
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
- ## Migration from v3
472
+ ## Error Handling
816
473
 
817
- No breaking changes! The SDK is fully backward compatible. New features are additive:
474
+ The SDK provides detailed error information:
818
475
 
819
476
  ```javascript
820
- // v3 code continues to work
821
- const { getPhoneNumber } = usePhoneAuth(config);
822
-
823
- // v4 adds new optional features
824
- const { getPhoneNumber, retryLastRequest } = usePhoneAuth({
825
- ...config,
826
- onCrossDeviceDetected: () => {}, // Optional
827
- onRetryAttempt: () => {} // Optional
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
- ## Debug Tools
499
+ ## Browser Support
832
500
 
833
- For development and testing, debug components are available in the `/examples/debug-components/` folder:
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
- - `PhoneAuthDebugger.jsx` - Comprehensive debugging interface
836
- - `AppEnhanced.jsx` - Production app with debug mode
507
+ ### Checking Support
837
508
 
838
- These are not included in the main bundle. Copy them to your project if needed.
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 AuthConfig {
846
- // Required endpoints
847
- endpoints: {
848
- prepare: string; // Your backend prepare endpoint
849
- process: string; // Your backend process endpoint
850
- polling?: string; // Optional custom polling endpoint
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
- ### Modal Customization (UI Mode)
539
+ ### Invoke Options
868
540
 
869
541
  ```typescript
870
- interface ModalOptions {
871
- className?: string; // Custom CSS class
872
- title?: string; // Modal title
873
- description?: string; // Modal description
874
- buttonText?: string; // Button label
875
- showCloseButton?: boolean; // Show close button (default: true)
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
- // High-level methods (handle everything)
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<HeadlessResult | Credential>
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
- retryLastRequest(): Promise<any>
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://api.glideidentity.com/docs)
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