@glideidentity/web-client-sdk 4.4.8-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 +938 -0
- package/dist/adapters/angular/client.service.d.ts +7 -0
- package/dist/adapters/angular/client.service.js +30 -0
- package/dist/adapters/angular/index.d.ts +3 -0
- package/dist/adapters/angular/index.js +18 -0
- package/dist/adapters/angular/phone-auth.service.d.ts +38 -0
- package/dist/adapters/angular/phone-auth.service.js +130 -0
- package/dist/adapters/react/index.d.ts +9 -0
- package/dist/adapters/react/index.js +28 -0
- package/dist/adapters/react/useClient.d.ts +26 -0
- package/dist/adapters/react/useClient.js +121 -0
- package/dist/adapters/react/usePhoneAuth.d.ts +23 -0
- package/dist/adapters/react/usePhoneAuth.js +95 -0
- package/dist/adapters/vanilla/client.d.ts +8 -0
- package/dist/adapters/vanilla/client.js +33 -0
- package/dist/adapters/vanilla/index.d.ts +3 -0
- package/dist/adapters/vanilla/index.js +18 -0
- package/dist/adapters/vanilla/phone-auth.d.ts +46 -0
- package/dist/adapters/vanilla/phone-auth.js +138 -0
- package/dist/adapters/vue/index.d.ts +10 -0
- package/dist/adapters/vue/index.js +36 -0
- package/dist/adapters/vue/useClient.d.ts +115 -0
- package/dist/adapters/vue/useClient.js +131 -0
- package/dist/adapters/vue/usePhoneAuth.d.ts +94 -0
- package/dist/adapters/vue/usePhoneAuth.js +103 -0
- package/dist/browser/web-client-sdk.min.js +2 -0
- package/dist/browser/web-client-sdk.min.js.LICENSE.txt +1 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +31 -0
- package/dist/core/client.d.ts +22 -0
- package/dist/core/client.js +77 -0
- package/dist/core/logger.d.ts +130 -0
- package/dist/core/logger.js +370 -0
- package/dist/core/phone-auth/api-types.d.ts +525 -0
- package/dist/core/phone-auth/api-types.js +215 -0
- package/dist/core/phone-auth/client.d.ts +187 -0
- package/dist/core/phone-auth/client.js +1353 -0
- package/dist/core/phone-auth/error-utils.d.ts +110 -0
- package/dist/core/phone-auth/error-utils.js +350 -0
- package/dist/core/phone-auth/index.d.ts +7 -0
- package/dist/core/phone-auth/index.js +47 -0
- package/dist/core/phone-auth/status-types.d.ts +107 -0
- package/dist/core/phone-auth/status-types.js +31 -0
- package/dist/core/phone-auth/strategies/desktop.d.ts +113 -0
- package/dist/core/phone-auth/strategies/desktop.js +502 -0
- package/dist/core/phone-auth/strategies/index.d.ts +11 -0
- package/dist/core/phone-auth/strategies/index.js +15 -0
- package/dist/core/phone-auth/strategies/link.d.ts +81 -0
- package/dist/core/phone-auth/strategies/link.js +265 -0
- package/dist/core/phone-auth/strategies/ts43.d.ts +32 -0
- package/dist/core/phone-auth/strategies/ts43.js +146 -0
- package/dist/core/phone-auth/strategies/types.d.ts +18 -0
- package/dist/core/phone-auth/strategies/types.js +6 -0
- package/dist/core/phone-auth/type-guards.d.ts +125 -0
- package/dist/core/phone-auth/type-guards.js +160 -0
- package/dist/core/phone-auth/types.d.ts +232 -0
- package/dist/core/phone-auth/types.js +93 -0
- package/dist/core/phone-auth/ui/mobile-debug-console.d.ts +25 -0
- package/dist/core/phone-auth/ui/mobile-debug-console.js +288 -0
- package/dist/core/phone-auth/ui/modal.d.ts +84 -0
- package/dist/core/phone-auth/ui/modal.js +574 -0
- package/dist/core/phone-auth/validation-utils.d.ts +66 -0
- package/dist/core/phone-auth/validation-utils.js +182 -0
- package/dist/core/types.d.ts +62 -0
- package/dist/core/types.js +2 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/version.js +5 -0
- package/dist/esm/adapters/angular/client.service.d.ts +7 -0
- package/dist/esm/adapters/angular/client.service.js +27 -0
- package/dist/esm/adapters/angular/index.d.ts +3 -0
- package/dist/esm/adapters/angular/index.js +4 -0
- package/dist/esm/adapters/angular/phone-auth.service.d.ts +38 -0
- package/dist/esm/adapters/angular/phone-auth.service.js +127 -0
- package/dist/esm/adapters/react/index.d.ts +9 -0
- package/dist/esm/adapters/react/index.js +8 -0
- package/dist/esm/adapters/react/useClient.d.ts +26 -0
- package/dist/esm/adapters/react/useClient.js +116 -0
- package/dist/esm/adapters/react/usePhoneAuth.d.ts +23 -0
- package/dist/esm/adapters/react/usePhoneAuth.js +92 -0
- package/dist/esm/adapters/vanilla/client.d.ts +8 -0
- package/dist/esm/adapters/vanilla/client.js +29 -0
- package/dist/esm/adapters/vanilla/index.d.ts +3 -0
- package/dist/esm/adapters/vanilla/index.js +4 -0
- package/dist/esm/adapters/vanilla/phone-auth.d.ts +46 -0
- package/dist/esm/adapters/vanilla/phone-auth.js +134 -0
- package/dist/esm/adapters/vue/index.d.ts +10 -0
- package/dist/esm/adapters/vue/index.js +11 -0
- package/dist/esm/adapters/vue/useClient.d.ts +115 -0
- package/dist/esm/adapters/vue/useClient.js +127 -0
- package/dist/esm/adapters/vue/usePhoneAuth.d.ts +94 -0
- package/dist/esm/adapters/vue/usePhoneAuth.js +100 -0
- package/dist/esm/browser.d.ts +7 -0
- package/dist/esm/browser.js +11 -0
- package/dist/esm/core/client.d.ts +22 -0
- package/dist/esm/core/client.js +70 -0
- package/dist/esm/core/logger.d.ts +130 -0
- package/dist/esm/core/logger.js +359 -0
- package/dist/esm/core/phone-auth/api-types.d.ts +525 -0
- package/dist/esm/core/phone-auth/api-types.js +203 -0
- package/dist/esm/core/phone-auth/client.d.ts +187 -0
- package/dist/esm/core/phone-auth/client.js +1316 -0
- package/dist/esm/core/phone-auth/error-utils.d.ts +110 -0
- package/dist/esm/core/phone-auth/error-utils.js +338 -0
- package/dist/esm/core/phone-auth/index.d.ts +7 -0
- package/dist/esm/core/phone-auth/index.js +6 -0
- package/dist/esm/core/phone-auth/status-types.d.ts +107 -0
- package/dist/esm/core/phone-auth/status-types.js +26 -0
- package/dist/esm/core/phone-auth/strategies/desktop.d.ts +113 -0
- package/dist/esm/core/phone-auth/strategies/desktop.js +496 -0
- package/dist/esm/core/phone-auth/strategies/index.d.ts +11 -0
- package/dist/esm/core/phone-auth/strategies/index.js +7 -0
- package/dist/esm/core/phone-auth/strategies/link.d.ts +81 -0
- package/dist/esm/core/phone-auth/strategies/link.js +261 -0
- package/dist/esm/core/phone-auth/strategies/ts43.d.ts +32 -0
- package/dist/esm/core/phone-auth/strategies/ts43.js +142 -0
- package/dist/esm/core/phone-auth/strategies/types.d.ts +18 -0
- package/dist/esm/core/phone-auth/strategies/types.js +5 -0
- package/dist/esm/core/phone-auth/type-guards.d.ts +125 -0
- package/dist/esm/core/phone-auth/type-guards.js +150 -0
- package/dist/esm/core/phone-auth/types.d.ts +232 -0
- package/dist/esm/core/phone-auth/types.js +76 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +25 -0
- package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +284 -0
- package/dist/esm/core/phone-auth/ui/modal.d.ts +84 -0
- package/dist/esm/core/phone-auth/ui/modal.js +570 -0
- package/dist/esm/core/phone-auth/validation-utils.d.ts +66 -0
- package/dist/esm/core/phone-auth/validation-utils.js +174 -0
- package/dist/esm/core/types.d.ts +62 -0
- package/dist/esm/core/types.js +1 -0
- package/dist/esm/core/version.d.ts +1 -0
- package/dist/esm/core/version.js +2 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +15 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +52 -0
- package/package.json +92 -0
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
*/
|
|
6
|
+
import type { StrategyHandler } from './types';
|
|
7
|
+
import type { PrepareResponse } from '../types';
|
|
8
|
+
export interface LinkAuthOptions {
|
|
9
|
+
/** Fixed polling interval in milliseconds (default: 2000) */
|
|
10
|
+
pollingInterval?: number;
|
|
11
|
+
/** Maximum polling attempts before timeout (default: 150 = 5 minutes with 2s interval) */
|
|
12
|
+
maxPollingAttempts?: number;
|
|
13
|
+
/** Custom polling endpoint (overrides backend-provided or configured endpoint) */
|
|
14
|
+
pollingEndpoint?: string;
|
|
15
|
+
/** Callback when link is opened */
|
|
16
|
+
onLinkOpened?: () => void;
|
|
17
|
+
/** Callback for polling status updates */
|
|
18
|
+
onStatusUpdate?: (status: PollingStatus) => void;
|
|
19
|
+
/** Callback when authentication times out */
|
|
20
|
+
onTimeout?: () => void;
|
|
21
|
+
/** Callback when authentication is cancelled by user */
|
|
22
|
+
onCancel?: () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface PollingStatus {
|
|
25
|
+
/** Current status of the authentication */
|
|
26
|
+
status: 'pending' | 'authenticated' | 'expired' | 'cancelled' | 'error';
|
|
27
|
+
/** Optional message */
|
|
28
|
+
message?: string;
|
|
29
|
+
/** Authentication result data if status is 'authenticated' */
|
|
30
|
+
data?: any;
|
|
31
|
+
}
|
|
32
|
+
export interface LinkAuthResult {
|
|
33
|
+
/** Whether authentication was successful */
|
|
34
|
+
authenticated: boolean;
|
|
35
|
+
/** Authentication credential if successful */
|
|
36
|
+
credential?: string;
|
|
37
|
+
/** Session info for subsequent requests */
|
|
38
|
+
session?: any;
|
|
39
|
+
/** Error message if authentication failed */
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class LinkHandler implements StrategyHandler {
|
|
43
|
+
private pollingInterval?;
|
|
44
|
+
private isPolling;
|
|
45
|
+
private isCancelled;
|
|
46
|
+
private onCancel?;
|
|
47
|
+
/**
|
|
48
|
+
* Invoke link-based authentication
|
|
49
|
+
* Opens authentication app while keeping user on current page
|
|
50
|
+
*/
|
|
51
|
+
invoke(data: PrepareResponse, options?: LinkAuthOptions): Promise<LinkAuthResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Open authentication link without navigating away
|
|
54
|
+
*/
|
|
55
|
+
private openAuthenticationLink;
|
|
56
|
+
/**
|
|
57
|
+
* Start polling for authentication status with constant interval
|
|
58
|
+
*/
|
|
59
|
+
private startPolling;
|
|
60
|
+
/**
|
|
61
|
+
* Stop polling
|
|
62
|
+
*/
|
|
63
|
+
private stopPolling;
|
|
64
|
+
/**
|
|
65
|
+
* Format response for backend processing
|
|
66
|
+
*/
|
|
67
|
+
formatResponse(response: LinkAuthResult): any;
|
|
68
|
+
/**
|
|
69
|
+
* Check if link strategy is supported
|
|
70
|
+
* Returns true for mobile devices (iOS and Android)
|
|
71
|
+
*/
|
|
72
|
+
isSupported(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Clean up resources (stop polling if active)
|
|
75
|
+
*/
|
|
76
|
+
cleanup(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Cancel the ongoing authentication
|
|
79
|
+
*/
|
|
80
|
+
cancel(): void;
|
|
81
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Link Strategy Handler
|
|
4
|
+
* Handles authentication via app links (iOS and Android)
|
|
5
|
+
* Opens authentication app while keeping user on current page
|
|
6
|
+
*/
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.LinkHandler = void 0;
|
|
18
|
+
class LinkHandler {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.isPolling = false;
|
|
21
|
+
this.isCancelled = false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Invoke link-based authentication
|
|
25
|
+
* Opens authentication app while keeping user on current page
|
|
26
|
+
*/
|
|
27
|
+
invoke(data, options) {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
// Extract link data from prepare response
|
|
30
|
+
const linkData = data.data;
|
|
31
|
+
if (!linkData || !linkData.url) {
|
|
32
|
+
throw new Error('Invalid link data: missing URL');
|
|
33
|
+
}
|
|
34
|
+
const sessionKey = data.session.session_key;
|
|
35
|
+
// Open authentication app without navigating away from current page
|
|
36
|
+
this.openAuthenticationLink(linkData.url);
|
|
37
|
+
// Notify that link was opened
|
|
38
|
+
if (options === null || options === void 0 ? void 0 : options.onLinkOpened) {
|
|
39
|
+
options.onLinkOpened();
|
|
40
|
+
}
|
|
41
|
+
// Start polling for authentication status
|
|
42
|
+
// Use constant interval (no exponential backoff)
|
|
43
|
+
return this.startPolling(sessionKey, linkData, options);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Open authentication link without navigating away
|
|
48
|
+
*/
|
|
49
|
+
openAuthenticationLink(url) {
|
|
50
|
+
// Always use window.open - Works best for App Clips and Android deep links
|
|
51
|
+
// Must be called in direct response to user action to avoid popup blocker
|
|
52
|
+
const newWindow = window.open(url, '_blank');
|
|
53
|
+
if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
|
|
54
|
+
// Popup was blocked - this can happen if not called from user gesture
|
|
55
|
+
console.warn('[LinkHandler] Failed to open app link - popup may have been blocked');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Start polling for authentication status with constant interval
|
|
60
|
+
*/
|
|
61
|
+
startPolling(sessionKey, linkData, options) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
const interval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || 2000; // Fixed 2 second interval
|
|
64
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 150; // 5 minutes with 2s interval
|
|
65
|
+
let attempts = 0;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
this.isPolling = true;
|
|
68
|
+
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
if (!this.isPolling) {
|
|
70
|
+
return; // Polling was stopped
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
attempts++;
|
|
74
|
+
// Check max attempts
|
|
75
|
+
if (attempts >= maxAttempts) {
|
|
76
|
+
this.stopPolling();
|
|
77
|
+
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
78
|
+
options.onTimeout();
|
|
79
|
+
}
|
|
80
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
81
|
+
options.onStatusUpdate({
|
|
82
|
+
status: 'expired',
|
|
83
|
+
message: 'Authentication timeout'
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
reject(new Error('Authentication timeout after 5 minutes'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Build public status endpoint URL
|
|
90
|
+
let statusUrl;
|
|
91
|
+
// First priority: use status_url from link data if provided
|
|
92
|
+
if (linkData.status_url) {
|
|
93
|
+
statusUrl = linkData.status_url;
|
|
94
|
+
console.log('[Link Auth] Using status URL from link data:', statusUrl);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
statusUrl = `https://api.glideidentity.app/public/public/status/${sessionKey}`;
|
|
98
|
+
}
|
|
99
|
+
// Poll public status endpoint - no authentication required
|
|
100
|
+
const response = yield fetch(statusUrl, {
|
|
101
|
+
method: 'GET',
|
|
102
|
+
headers: {
|
|
103
|
+
'Accept': 'application/json'
|
|
104
|
+
// No Authorization header needed for public endpoint
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Handle based on HTTP status code
|
|
108
|
+
if (response.status === 200) {
|
|
109
|
+
// Session is active (pending or completed)
|
|
110
|
+
const result = yield response.json();
|
|
111
|
+
if (result.status === 'completed') {
|
|
112
|
+
// Authentication completed successfully
|
|
113
|
+
this.stopPolling();
|
|
114
|
+
// Authentication completed successfully
|
|
115
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
116
|
+
options.onStatusUpdate({
|
|
117
|
+
status: 'authenticated',
|
|
118
|
+
message: 'Authentication successful',
|
|
119
|
+
data: result
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// Return the authentication result
|
|
123
|
+
resolve({
|
|
124
|
+
authenticated: true,
|
|
125
|
+
credential: result.credential || sessionKey,
|
|
126
|
+
session: result.session || {
|
|
127
|
+
session_key: sessionKey,
|
|
128
|
+
status: result.status,
|
|
129
|
+
protocol: result.protocol || 'link',
|
|
130
|
+
created_at: result.created_at,
|
|
131
|
+
last_updated: result.last_updated
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else if (result.status === 'pending') {
|
|
136
|
+
// Continue polling
|
|
137
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
138
|
+
options.onStatusUpdate({
|
|
139
|
+
status: 'pending',
|
|
140
|
+
message: 'Waiting for app authentication...'
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (response.status === 410) {
|
|
146
|
+
// Session expired
|
|
147
|
+
this.stopPolling();
|
|
148
|
+
const errorData = yield response.json().catch(() => ({ message: 'Session expired' }));
|
|
149
|
+
if (options === null || options === void 0 ? void 0 : options.onTimeout) {
|
|
150
|
+
options.onTimeout();
|
|
151
|
+
}
|
|
152
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
153
|
+
options.onStatusUpdate({
|
|
154
|
+
status: 'expired',
|
|
155
|
+
message: errorData.message || 'Session expired'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
reject(new Error(errorData.message || 'Session expired'));
|
|
159
|
+
}
|
|
160
|
+
else if (response.status === 422) {
|
|
161
|
+
// Authentication failed
|
|
162
|
+
this.stopPolling();
|
|
163
|
+
const errorData = yield response.json().catch(() => ({ message: 'Authentication failed' }));
|
|
164
|
+
const isUserCancelled = errorData.code === 'USER_CANCELLED';
|
|
165
|
+
const errorMsg = isUserCancelled
|
|
166
|
+
? 'User cancelled authentication'
|
|
167
|
+
: (errorData.message || 'Verification failed');
|
|
168
|
+
// Authentication failed
|
|
169
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
170
|
+
options.onStatusUpdate({
|
|
171
|
+
status: 'error',
|
|
172
|
+
message: errorMsg
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
reject(new Error(errorMsg));
|
|
176
|
+
}
|
|
177
|
+
else if (response.status === 404) {
|
|
178
|
+
// Session not found
|
|
179
|
+
this.stopPolling();
|
|
180
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
181
|
+
options.onStatusUpdate({
|
|
182
|
+
status: 'error',
|
|
183
|
+
message: 'Session not found'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
reject(new Error('Session not found'));
|
|
187
|
+
}
|
|
188
|
+
else if (response.status === 400) {
|
|
189
|
+
// Invalid session key
|
|
190
|
+
this.stopPolling();
|
|
191
|
+
const errorData = yield response.json().catch(() => ({ message: 'Invalid session key' }));
|
|
192
|
+
reject(new Error(errorData.message || 'Invalid session key'));
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Unexpected status - continue polling
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
// Network or other error - continue polling
|
|
200
|
+
if (options === null || options === void 0 ? void 0 : options.onStatusUpdate) {
|
|
201
|
+
options.onStatusUpdate({
|
|
202
|
+
status: 'pending',
|
|
203
|
+
message: `Connection issue, retrying... (${attempts}/${maxAttempts})`
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
// Start initial poll immediately
|
|
209
|
+
poll();
|
|
210
|
+
// Set up constant interval polling (no backoff)
|
|
211
|
+
this.pollingInterval = setInterval(poll, interval);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Stop polling
|
|
217
|
+
*/
|
|
218
|
+
stopPolling() {
|
|
219
|
+
this.isPolling = false;
|
|
220
|
+
if (this.pollingInterval) {
|
|
221
|
+
clearInterval(this.pollingInterval);
|
|
222
|
+
this.pollingInterval = undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Format response for backend processing
|
|
227
|
+
*/
|
|
228
|
+
formatResponse(response) {
|
|
229
|
+
if (!response.authenticated || !response.credential) {
|
|
230
|
+
throw new Error('Authentication not completed');
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
credential: response.credential,
|
|
234
|
+
session: response.session,
|
|
235
|
+
type: 'link'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check if link strategy is supported
|
|
240
|
+
* Returns true for mobile devices (iOS and Android)
|
|
241
|
+
*/
|
|
242
|
+
isSupported() {
|
|
243
|
+
// Link strategy is supported on mobile devices
|
|
244
|
+
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
245
|
+
return isMobile;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Clean up resources (stop polling if active)
|
|
249
|
+
*/
|
|
250
|
+
cleanup() {
|
|
251
|
+
this.stopPolling();
|
|
252
|
+
this.isCancelled = false;
|
|
253
|
+
this.onCancel = undefined;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Cancel the ongoing authentication
|
|
257
|
+
*/
|
|
258
|
+
cancel() {
|
|
259
|
+
var _a;
|
|
260
|
+
this.isCancelled = true;
|
|
261
|
+
this.stopPolling();
|
|
262
|
+
(_a = this.onCancel) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
exports.LinkHandler = LinkHandler;
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
*/
|
|
6
|
+
import type { StrategyHandler } from './types';
|
|
7
|
+
import type { PrepareResponse } from '../types';
|
|
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
|
+
*/
|
|
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
|
+
*/
|
|
18
|
+
formatResponse(response: any): any;
|
|
19
|
+
/**
|
|
20
|
+
* Check if Digital Credentials API is supported
|
|
21
|
+
*/
|
|
22
|
+
isSupported(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Get browser support information
|
|
25
|
+
*/
|
|
26
|
+
getBrowserSupportInfo(): {
|
|
27
|
+
supported: boolean;
|
|
28
|
+
browser: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
helpUrl?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TS-43 Strategy Handler
|
|
4
|
+
* Handles Digital Credentials API authentication for Android/Chromium
|
|
5
|
+
* Properly manages session objects with session_key, nonce, and enc_key
|
|
6
|
+
*/
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.TS43Handler = void 0;
|
|
18
|
+
class TS43Handler {
|
|
19
|
+
/**
|
|
20
|
+
* Invoke TS-43 authentication using Digital Credentials API
|
|
21
|
+
* The data structure from backend is already in the correct format for navigator.credentials.get
|
|
22
|
+
*/
|
|
23
|
+
invoke(data) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
// Validate session structure - only session_key is actually required
|
|
26
|
+
if (!data.session || !data.session.session_key) {
|
|
27
|
+
throw new Error('Invalid TS43 session: missing required session_key');
|
|
28
|
+
}
|
|
29
|
+
// Check if Digital Credentials API is available
|
|
30
|
+
if (!this.isSupported()) {
|
|
31
|
+
throw new Error('Digital Credentials API not supported in this browser');
|
|
32
|
+
}
|
|
33
|
+
// Extract TS43 data from the prepare response
|
|
34
|
+
const ts43Data = data.data;
|
|
35
|
+
// The data is already in the correct format from backend
|
|
36
|
+
// Just pass it through to navigator.credentials.get
|
|
37
|
+
const credentialRequest = {
|
|
38
|
+
digital: {
|
|
39
|
+
requests: [{
|
|
40
|
+
protocol: ts43Data.protocol, // e.g., "openid4vp-v1-unsigned"
|
|
41
|
+
data: ts43Data.data // Pass the entire data object as-is
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
// @ts-ignore - Digital Credentials API types not yet in TypeScript
|
|
47
|
+
const credential = yield navigator.credentials.get(credentialRequest);
|
|
48
|
+
if (!credential) {
|
|
49
|
+
throw new Error('No credential received from Digital Credentials API');
|
|
50
|
+
}
|
|
51
|
+
// @ts-ignore - credential.data is not typed yet
|
|
52
|
+
return credential;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Handle browser-specific errors
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
if (error.name === 'NotAllowedError') {
|
|
58
|
+
throw new Error('User denied the authentication request');
|
|
59
|
+
}
|
|
60
|
+
if (error.name === 'NotSupportedError') {
|
|
61
|
+
throw new Error('Digital Credentials API not supported');
|
|
62
|
+
}
|
|
63
|
+
if (error.name === 'AbortError') {
|
|
64
|
+
throw new Error('Authentication was aborted');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Format the credential response for backend processing
|
|
73
|
+
* Include the session key so backend can retrieve the full session
|
|
74
|
+
*/
|
|
75
|
+
formatResponse(response) {
|
|
76
|
+
var _a;
|
|
77
|
+
// Extract vp_token from the response
|
|
78
|
+
const vpToken = (_a = response.data) === null || _a === void 0 ? void 0 : _a.vp_token;
|
|
79
|
+
if (!vpToken) {
|
|
80
|
+
throw new Error('Invalid TS43 response: missing vp_token');
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
vp_token: vpToken,
|
|
84
|
+
type: 'ts43'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if Digital Credentials API is supported
|
|
89
|
+
*/
|
|
90
|
+
isSupported() {
|
|
91
|
+
if (typeof window === 'undefined') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Check for DigitalCredential constructor
|
|
95
|
+
// @ts-ignore - DigitalCredential not yet in TypeScript
|
|
96
|
+
return 'DigitalCredential' in window;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get browser support information
|
|
100
|
+
*/
|
|
101
|
+
getBrowserSupportInfo() {
|
|
102
|
+
if (typeof window === 'undefined') {
|
|
103
|
+
return {
|
|
104
|
+
supported: false,
|
|
105
|
+
browser: 'unknown',
|
|
106
|
+
message: 'Not running in a browser environment'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const userAgent = navigator.userAgent;
|
|
110
|
+
const isChrome = /Chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor);
|
|
111
|
+
const isEdge = /Edg\//.test(userAgent);
|
|
112
|
+
const isAndroid = /Android/.test(userAgent);
|
|
113
|
+
const isSupported = this.isSupported();
|
|
114
|
+
if (isSupported) {
|
|
115
|
+
return {
|
|
116
|
+
supported: true,
|
|
117
|
+
browser: isChrome ? 'Chrome' : isEdge ? 'Edge' : 'Chromium'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Provide specific guidance based on browser
|
|
121
|
+
if ((isChrome || isEdge) && isAndroid) {
|
|
122
|
+
return {
|
|
123
|
+
supported: false,
|
|
124
|
+
browser: isChrome ? 'Chrome' : 'Edge',
|
|
125
|
+
message: 'Digital Credentials API requires Chrome 128+ on Android. Please update your browser.',
|
|
126
|
+
helpUrl: 'https://play.google.com/store/apps/details?id=com.android.chrome'
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (isChrome || isEdge) {
|
|
130
|
+
return {
|
|
131
|
+
supported: false,
|
|
132
|
+
browser: isChrome ? 'Chrome' : 'Edge',
|
|
133
|
+
message: 'Digital Credentials API is not enabled. Please enable the #web-identity-digital-credentials flag.',
|
|
134
|
+
helpUrl: isChrome
|
|
135
|
+
? 'chrome://flags/#web-identity-digital-credentials'
|
|
136
|
+
: 'edge://flags/#web-identity-digital-credentials'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
supported: false,
|
|
141
|
+
browser: 'unsupported',
|
|
142
|
+
message: 'Your browser does not support Digital Credentials API'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.TS43Handler = TS43Handler;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Handler Interface
|
|
3
|
+
* Defines the contract for authentication strategy implementations
|
|
4
|
+
*/
|
|
5
|
+
export interface StrategyHandler {
|
|
6
|
+
/**
|
|
7
|
+
* Invoke authentication using strategy-specific data
|
|
8
|
+
*/
|
|
9
|
+
invoke(data: any): Promise<any>;
|
|
10
|
+
/**
|
|
11
|
+
* Format response for backend processing
|
|
12
|
+
*/
|
|
13
|
+
formatResponse(response: any): any;
|
|
14
|
+
/**
|
|
15
|
+
* Check if this strategy is supported in the current environment
|
|
16
|
+
*/
|
|
17
|
+
isSupported(): boolean;
|
|
18
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
*/
|
|
7
|
+
import type { HeadlessResult } from './api-types';
|
|
8
|
+
/**
|
|
9
|
+
* Type guard to check if the result is a HeadlessResult (headless mode)
|
|
10
|
+
* or a Credential (UI mode).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const result = await invokeSecurePrompt(sdkRequest);
|
|
15
|
+
*
|
|
16
|
+
* if (isHeadlessResult(result)) {
|
|
17
|
+
* // TypeScript knows this is HeadlessResult
|
|
18
|
+
* console.log(result.strategy);
|
|
19
|
+
* await result.trigger();
|
|
20
|
+
* } else {
|
|
21
|
+
* // TypeScript knows this is a Credential
|
|
22
|
+
* const processedResult = await verifyPhoneNumberCredential(result, session);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function isHeadlessResult(result: any): result is HeadlessResult;
|
|
27
|
+
/**
|
|
28
|
+
* Type guard to check if the result is a Credential (UI mode response).
|
|
29
|
+
* A credential is either a string token or an object without HeadlessResult 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
|
+
*/
|
|
39
|
+
export declare function isCredential(result: any): result is string | {
|
|
40
|
+
[aggregator_id: string]: string | string[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Type guard to check if a HeadlessResult is using the Link strategy.
|
|
44
|
+
* Link strategy involves opening an app link (App Clip on iOS, app on Android).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* if (isHeadlessResult(result) && isLinkStrategy(result)) {
|
|
49
|
+
* // Show custom button for opening app
|
|
50
|
+
* myButton.onclick = () => result.trigger();
|
|
51
|
+
* await result.pollingPromise;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function isLinkStrategy(result: HeadlessResult): result is HeadlessResult & {
|
|
56
|
+
strategy: 'link';
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Type guard to check if a HeadlessResult is using the TS43 strategy.
|
|
60
|
+
* TS43 strategy uses the browser's Digital Credentials API.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* if (isHeadlessResult(result) && isTS43Strategy(result)) {
|
|
65
|
+
* // Invoke credential API immediately or on button click
|
|
66
|
+
* const credential = await result.trigger();
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function isTS43Strategy(result: HeadlessResult): result is HeadlessResult & {
|
|
71
|
+
strategy: 'ts43';
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Type guard to check if a HeadlessResult is using the Desktop strategy.
|
|
75
|
+
* Desktop strategy involves QR codes for cross-device authentication.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* if (isHeadlessResult(result) && isDesktopStrategy(result)) {
|
|
80
|
+
* // Show custom QR code UI
|
|
81
|
+
* displayQRCode(result.qrCodeUrl);
|
|
82
|
+
* await result.pollingPromise;
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function isDesktopStrategy(result: HeadlessResult): result is HeadlessResult & {
|
|
87
|
+
strategy: 'desktop';
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Helper function to safely get the authentication strategy from any result.
|
|
91
|
+
* Returns undefined if the result is not a HeadlessResult.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const strategy = getStrategy(result);
|
|
96
|
+
* if (strategy === 'link') {
|
|
97
|
+
* // Handle link strategy
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function getStrategy(result: any): 'link' | 'ts43' | 'desktop' | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Helper function to determine if a result requires polling.
|
|
104
|
+
* Link and Desktop strategies require polling, TS43 does not.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* if (requiresPolling(result)) {
|
|
109
|
+
* await result.pollingPromise;
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function requiresPolling(result: any): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Helper function to determine if a result requires user interaction.
|
|
116
|
+
* All headless strategies require some form of user interaction.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* if (requiresUserAction(result)) {
|
|
121
|
+
* showActionButton();
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function requiresUserAction(result: any): boolean;
|