@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
|
@@ -170,6 +170,10 @@ export class PhoneAuthClient {
|
|
|
170
170
|
}
|
|
171
171
|
// Step 3: Process the response through appropriate endpoint
|
|
172
172
|
const credential = credentialResponse;
|
|
173
|
+
// Validate use_case is provided for endpoint selection
|
|
174
|
+
if (!options.use_case) {
|
|
175
|
+
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
176
|
+
}
|
|
173
177
|
const result = options.use_case === API.USE_CASE.GET_PHONE_NUMBER
|
|
174
178
|
? yield this.getPhoneNumber(credential, preparedRequest.session)
|
|
175
179
|
: yield this.verifyPhoneNumber(credential, preparedRequest.session);
|
|
@@ -278,16 +282,7 @@ export class PhoneAuthClient {
|
|
|
278
282
|
*/
|
|
279
283
|
preparePhoneRequest(options) {
|
|
280
284
|
return __awaiter(this, void 0, void 0, function* () {
|
|
281
|
-
|
|
282
|
-
// const useCaseValidation = validateUseCaseRequirements(options.use_case, options.phone_number);
|
|
283
|
-
// if (!useCaseValidation.valid) {
|
|
284
|
-
// throw this.createError(
|
|
285
|
-
// PhoneAuthErrorCode.BAD_REQUEST,
|
|
286
|
-
// useCaseValidation.error!,
|
|
287
|
-
// { field: 'use_case' }
|
|
288
|
-
// );
|
|
289
|
-
// }
|
|
290
|
-
var _a, _b;
|
|
285
|
+
var _a, _b, _c;
|
|
291
286
|
// Validate phone number if provided
|
|
292
287
|
if (options.phone_number) {
|
|
293
288
|
const phoneValidation = validatePhoneNumber(options.phone_number);
|
|
@@ -309,8 +304,12 @@ export class PhoneAuthClient {
|
|
|
309
304
|
throw this.createError(PhoneAuthErrorCode.BAD_REQUEST, consentValidation.error, { field: 'consent_data' });
|
|
310
305
|
}
|
|
311
306
|
}
|
|
307
|
+
// Validate use_case is provided (unless only parent_session_id is given)
|
|
308
|
+
if (!options.use_case && !(((_a = options.options) === null || _a === void 0 ? void 0 : _a.parent_session_id) && !options.phone_number && !options.plmn)) {
|
|
309
|
+
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
310
|
+
}
|
|
312
311
|
// Validate required parameters based on use case
|
|
313
|
-
if (!options.phone_number && !options.plmn && !((
|
|
312
|
+
if (!options.phone_number && !options.plmn && !((_b = options.options) === null || _b === void 0 ? void 0 : _b.parent_session_id)) {
|
|
314
313
|
// Provide specific error message based on use case
|
|
315
314
|
if (options.use_case === API.USE_CASE.GET_PHONE_NUMBER) {
|
|
316
315
|
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'PLMN (MCC/MNC) is required for GetPhoneNumber. Please provide carrier network information.', { field: 'plmn', useCase: 'GetPhoneNumber' });
|
|
@@ -318,19 +317,15 @@ export class PhoneAuthClient {
|
|
|
318
317
|
else if (options.use_case === API.USE_CASE.VERIFY_PHONE_NUMBER) {
|
|
319
318
|
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'Phone number is required for VerifyPhoneNumber', { field: 'phoneNumber', useCase: 'VerifyPhoneNumber' });
|
|
320
319
|
}
|
|
321
|
-
else if (!options.use_case) {
|
|
322
|
-
// If no use case, that's an error
|
|
323
|
-
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
324
|
-
}
|
|
325
320
|
else {
|
|
326
321
|
// Fallback for other use cases
|
|
327
322
|
throw this.createError(PhoneAuthErrorCode.MISSING_PARAMETERS, 'Either phone number or PLMN (MCC/MNC) must be provided', { field: 'phoneNumber,plmn' });
|
|
328
323
|
}
|
|
329
324
|
}
|
|
330
|
-
//
|
|
331
|
-
if (((
|
|
325
|
+
// Log parent session usage
|
|
326
|
+
if (((_c = options.options) === null || _c === void 0 ? void 0 : _c.parent_session_id) && !options.phone_number && !options.plmn) {
|
|
332
327
|
if (this.debug) {
|
|
333
|
-
console.log('[PhoneAuth] Using parent_session_id
|
|
328
|
+
console.log('[PhoneAuth] Using parent_session_id: %s, use_case: %s', options.options.parent_session_id, options.use_case || 'not provided');
|
|
334
329
|
}
|
|
335
330
|
}
|
|
336
331
|
const requestId = `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -345,11 +340,13 @@ export class PhoneAuthClient {
|
|
|
345
340
|
throw this.createError(PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, 'Failed to generate valid nonce', { field: 'nonce' });
|
|
346
341
|
}
|
|
347
342
|
// Build properly typed request body according to API specification
|
|
343
|
+
// Be permissive - backend will ignore extra fields if not needed
|
|
348
344
|
const requestBody = {
|
|
349
|
-
//
|
|
345
|
+
// Include use_case if provided (optional when parent_session_id is given)
|
|
350
346
|
use_case: options.use_case,
|
|
351
|
-
//
|
|
347
|
+
// Include phone_number if provided (backend ignores if not needed)
|
|
352
348
|
phone_number: options.phone_number,
|
|
349
|
+
// Include PLMN if provided
|
|
353
350
|
plmn: options.plmn ? {
|
|
354
351
|
mcc: options.plmn.mcc,
|
|
355
352
|
mnc: options.plmn.mnc
|
|
@@ -381,7 +378,7 @@ export class PhoneAuthClient {
|
|
|
381
378
|
// Always include the HTTP status from the response
|
|
382
379
|
errorDetails = Object.assign(Object.assign({}, errorDetails), { status: response.status });
|
|
383
380
|
}
|
|
384
|
-
catch (
|
|
381
|
+
catch (_d) {
|
|
385
382
|
// If JSON parsing fails, use status text
|
|
386
383
|
errorDetails = { status: response.status, statusText: response.statusText };
|
|
387
384
|
}
|
|
@@ -478,7 +475,7 @@ export class PhoneAuthClient {
|
|
|
478
475
|
// This ensures we work with plain objects for browser APIs
|
|
479
476
|
// Vue's reactivity system wraps objects in Proxies which can interfere
|
|
480
477
|
// with browser APIs like Digital Credentials API that expect plain objects
|
|
481
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
478
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
482
479
|
// Try structuredClone first (modern browsers), but catch errors and fallback to JSON method
|
|
483
480
|
let plainResponse;
|
|
484
481
|
try {
|
|
@@ -649,12 +646,15 @@ export class PhoneAuthClient {
|
|
|
649
646
|
});
|
|
650
647
|
// TS43 always auto-triggers (no SDK UI ever)
|
|
651
648
|
// The Digital Credentials API provides its own OS-level UI
|
|
652
|
-
|
|
649
|
+
// Use a wrapper object to allow updating the promise reference
|
|
650
|
+
const credentialWrapper = {
|
|
651
|
+
promise: null
|
|
652
|
+
};
|
|
653
653
|
try {
|
|
654
654
|
// Always try to trigger immediately
|
|
655
655
|
const vpToken = yield enhancedTriggerTS43();
|
|
656
656
|
// Convert to AuthCredential format
|
|
657
|
-
|
|
657
|
+
credentialWrapper.promise = Promise.resolve({
|
|
658
658
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
659
659
|
session: plainResponse.session,
|
|
660
660
|
authenticated: true
|
|
@@ -662,36 +662,46 @@ export class PhoneAuthClient {
|
|
|
662
662
|
}
|
|
663
663
|
catch (error) {
|
|
664
664
|
// If auto-trigger fails, create a rejected promise
|
|
665
|
-
|
|
665
|
+
credentialWrapper.promise = Promise.reject(error);
|
|
666
666
|
}
|
|
667
667
|
// Handle based on execution mode
|
|
668
668
|
if (executionMode === 'extended') {
|
|
669
669
|
// Extended mode - return control methods
|
|
670
|
-
|
|
670
|
+
const response = {
|
|
671
671
|
strategy: 'ts43',
|
|
672
672
|
session: plainResponse.session,
|
|
673
|
-
credential:
|
|
673
|
+
credential: credentialWrapper.promise, // Initial value
|
|
674
674
|
// Re-trigger credential request
|
|
675
675
|
trigger: () => __awaiter(this, void 0, void 0, function* () {
|
|
676
676
|
const vpToken = yield enhancedTriggerTS43();
|
|
677
|
-
// Update the credential promise
|
|
678
|
-
|
|
677
|
+
// Update the credential promise in the wrapper
|
|
678
|
+
credentialWrapper.promise = Promise.resolve({
|
|
679
679
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
680
680
|
session: plainResponse.session,
|
|
681
681
|
authenticated: true
|
|
682
682
|
});
|
|
683
|
+
// Return void as per interface
|
|
683
684
|
}),
|
|
684
685
|
cancel: () => {
|
|
685
686
|
// TS43 doesn't have a way to cancel once triggered
|
|
686
687
|
// but we can reject the promise
|
|
687
|
-
|
|
688
|
+
credentialWrapper.promise = Promise.reject(this.createError(PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
|
|
688
689
|
}
|
|
689
690
|
};
|
|
691
|
+
// Define credential as a getter that always returns the current promise
|
|
692
|
+
Object.defineProperty(response, 'credential', {
|
|
693
|
+
get() {
|
|
694
|
+
return credentialWrapper.promise;
|
|
695
|
+
},
|
|
696
|
+
enumerable: true,
|
|
697
|
+
configurable: true
|
|
698
|
+
});
|
|
699
|
+
return response;
|
|
690
700
|
}
|
|
691
701
|
else {
|
|
692
702
|
// Standard mode - just return credential
|
|
693
703
|
// Wait for and return the credential
|
|
694
|
-
const credential = yield
|
|
704
|
+
const credential = yield credentialWrapper.promise;
|
|
695
705
|
// Return in standard format
|
|
696
706
|
return {
|
|
697
707
|
[plainResponse.session.session_key]: credential.credential
|
|
@@ -721,7 +731,7 @@ export class PhoneAuthClient {
|
|
|
721
731
|
const pollingEndpointToUse = (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) ||
|
|
722
732
|
(desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingEndpoint) ||
|
|
723
733
|
((_m = this.config.endpoints) === null || _m === void 0 ? void 0 : _m.polling);
|
|
724
|
-
const pollingOptions = Object.assign(Object.assign({}, desktopOptions), { pollingEndpoint: pollingEndpointToUse, pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.maxPollingAttempts) || this.config.maxPollingAttempts ||
|
|
734
|
+
const pollingOptions = Object.assign(Object.assign({}, desktopOptions), { pollingEndpoint: pollingEndpointToUse, pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 30, onQRCodeReady: undefined, onStatusUpdate: undefined });
|
|
725
735
|
// Decide whether to show modal based on preventDefaultUI
|
|
726
736
|
const showModal = !preventDefaultUI;
|
|
727
737
|
let modal;
|
|
@@ -784,31 +794,79 @@ export class PhoneAuthClient {
|
|
|
784
794
|
// Handle based on execution mode
|
|
785
795
|
if (executionMode === 'extended') {
|
|
786
796
|
// Extended mode - return control methods
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
797
|
+
let credentialResolve = null;
|
|
798
|
+
let credentialReject = null;
|
|
799
|
+
let pollingStarted = false;
|
|
800
|
+
// Create a promise that will remain pending until polling starts
|
|
801
|
+
const credentialPromise = new Promise((resolve, reject) => {
|
|
802
|
+
credentialResolve = resolve;
|
|
803
|
+
credentialReject = reject;
|
|
804
|
+
// If modal is shown, start polling immediately
|
|
805
|
+
if (showModal) {
|
|
806
|
+
pollingStarted = true;
|
|
807
|
+
startPolling()
|
|
808
|
+
.then(resolve)
|
|
809
|
+
.catch(reject);
|
|
810
|
+
}
|
|
811
|
+
// Otherwise, promise remains pending until start_polling() is called
|
|
791
812
|
});
|
|
792
|
-
|
|
813
|
+
// Create wrapped functions
|
|
814
|
+
const wrappedStartPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
815
|
+
if (pollingStarted) {
|
|
816
|
+
throw this.createError(PhoneAuthErrorCode.INVALID_SESSION_STATE, 'Polling has already been started');
|
|
817
|
+
}
|
|
818
|
+
pollingStarted = true;
|
|
819
|
+
try {
|
|
820
|
+
const result = yield startPolling();
|
|
821
|
+
if (credentialResolve) {
|
|
822
|
+
credentialResolve(result);
|
|
823
|
+
}
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
catch (err) {
|
|
827
|
+
if (credentialReject) {
|
|
828
|
+
credentialReject(err);
|
|
829
|
+
}
|
|
830
|
+
throw err;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
const wrappedStopPolling = () => {
|
|
834
|
+
handler.cleanup();
|
|
835
|
+
if (modal)
|
|
836
|
+
modal.close();
|
|
837
|
+
};
|
|
838
|
+
const wrappedCancel = () => {
|
|
839
|
+
handler.cancel();
|
|
840
|
+
handler.cleanup();
|
|
841
|
+
if (modal)
|
|
842
|
+
modal.close();
|
|
843
|
+
// Reject the credential promise if it's still pending
|
|
844
|
+
if (!pollingStarted && credentialReject) {
|
|
845
|
+
credentialReject(this.createError(PhoneAuthErrorCode.USER_DENIED, 'Desktop authentication cancelled by user'));
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
// Create the response object with a getter for is_polling
|
|
849
|
+
const response = {
|
|
793
850
|
strategy: 'desktop',
|
|
794
851
|
session: plainResponse.session,
|
|
795
852
|
credential: credentialPromise,
|
|
796
853
|
qr_code_data: qrCodeDataSnakeCase,
|
|
797
854
|
modal_ref: modalRef,
|
|
798
|
-
start_polling:
|
|
799
|
-
stop_polling:
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
},
|
|
804
|
-
cancel: () => {
|
|
805
|
-
handler.cancel();
|
|
806
|
-
handler.cleanup();
|
|
807
|
-
if (modal)
|
|
808
|
-
modal.close();
|
|
809
|
-
},
|
|
810
|
-
is_polling: showModal // If modal shown, polling already started
|
|
855
|
+
start_polling: wrappedStartPolling,
|
|
856
|
+
stop_polling: wrappedStopPolling,
|
|
857
|
+
cancel: wrappedCancel,
|
|
858
|
+
// This will be replaced with a getter
|
|
859
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
811
860
|
};
|
|
861
|
+
// Define is_polling as a getter that returns current state
|
|
862
|
+
Object.defineProperty(response, 'is_polling', {
|
|
863
|
+
get() {
|
|
864
|
+
return handler.isPolling();
|
|
865
|
+
},
|
|
866
|
+
enumerable: true,
|
|
867
|
+
configurable: true
|
|
868
|
+
});
|
|
869
|
+
return response;
|
|
812
870
|
}
|
|
813
871
|
else {
|
|
814
872
|
// Standard mode - return credential when complete
|
|
@@ -864,30 +922,70 @@ export class PhoneAuthClient {
|
|
|
864
922
|
triggerLink();
|
|
865
923
|
}
|
|
866
924
|
// Start polling in the background
|
|
925
|
+
console.log('[PhoneAuth Client] Link polling config:', {
|
|
926
|
+
fromInvokeOptions: invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint,
|
|
927
|
+
fromClientConfig: (_o = this.config.endpoints) === null || _o === void 0 ? void 0 : _o.polling,
|
|
928
|
+
finalPollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_p = this.config.endpoints) === null || _p === void 0 ? void 0 : _p.polling)
|
|
929
|
+
});
|
|
867
930
|
const pollingOptions = {
|
|
868
|
-
pollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((
|
|
931
|
+
pollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_q = this.config.endpoints) === null || _q === void 0 ? void 0 : _q.polling),
|
|
869
932
|
pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || this.config.pollingInterval || 2000,
|
|
870
|
-
maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || this.config.maxPollingAttempts ||
|
|
933
|
+
maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 30,
|
|
871
934
|
onLinkOpened: undefined,
|
|
872
935
|
onStatusUpdate: undefined
|
|
873
936
|
};
|
|
874
|
-
|
|
875
|
-
const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
876
|
-
if (result.authenticated && result.credential) {
|
|
877
|
-
return {
|
|
878
|
-
credential: result.credential,
|
|
879
|
-
session: plainResponse.session,
|
|
880
|
-
authenticated: true
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
885
|
-
}
|
|
886
|
-
});
|
|
937
|
+
console.log('[PhoneAuth Client] Final Link polling options:', pollingOptions);
|
|
887
938
|
// Handle based on execution mode
|
|
888
939
|
if (executionMode === 'extended') {
|
|
889
|
-
// Extended mode - return control methods
|
|
890
|
-
|
|
940
|
+
// Extended mode - return control methods without starting polling immediately
|
|
941
|
+
let credentialResolve = null;
|
|
942
|
+
let credentialReject = null;
|
|
943
|
+
let pollingStarted = false;
|
|
944
|
+
let pollingPromise = null;
|
|
945
|
+
// Create a pending promise that will be resolved when polling completes
|
|
946
|
+
const credentialPromise = new Promise((resolve, reject) => {
|
|
947
|
+
credentialResolve = resolve;
|
|
948
|
+
credentialReject = reject;
|
|
949
|
+
});
|
|
950
|
+
// Function to start polling (only once)
|
|
951
|
+
const startPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
952
|
+
if (pollingStarted) {
|
|
953
|
+
throw this.createError(PhoneAuthErrorCode.INVALID_SESSION_STATE, 'Polling has already been started');
|
|
954
|
+
}
|
|
955
|
+
pollingStarted = true;
|
|
956
|
+
try {
|
|
957
|
+
// Store the polling promise to ensure we use the same one
|
|
958
|
+
if (!pollingPromise) {
|
|
959
|
+
pollingPromise = handler.invoke(plainResponse, pollingOptions);
|
|
960
|
+
}
|
|
961
|
+
const result = yield pollingPromise;
|
|
962
|
+
if (result.authenticated && result.credential) {
|
|
963
|
+
const authCredential = {
|
|
964
|
+
credential: result.credential,
|
|
965
|
+
session: plainResponse.session,
|
|
966
|
+
authenticated: true
|
|
967
|
+
};
|
|
968
|
+
if (credentialResolve) {
|
|
969
|
+
credentialResolve(authCredential);
|
|
970
|
+
}
|
|
971
|
+
return authCredential;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
const error = this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
975
|
+
if (credentialReject) {
|
|
976
|
+
credentialReject(error);
|
|
977
|
+
}
|
|
978
|
+
throw error;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch (err) {
|
|
982
|
+
if (credentialReject) {
|
|
983
|
+
credentialReject(err);
|
|
984
|
+
}
|
|
985
|
+
throw err;
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
const response = {
|
|
891
989
|
strategy: 'link',
|
|
892
990
|
session: plainResponse.session,
|
|
893
991
|
credential: credentialPromise,
|
|
@@ -896,22 +994,44 @@ export class PhoneAuthClient {
|
|
|
896
994
|
},
|
|
897
995
|
// Re-open the app link
|
|
898
996
|
trigger: triggerLink,
|
|
899
|
-
// Polling control
|
|
900
|
-
start_polling:
|
|
901
|
-
credential: result.credential || '',
|
|
902
|
-
session: plainResponse.session,
|
|
903
|
-
authenticated: result.authenticated
|
|
904
|
-
})),
|
|
997
|
+
// Polling control - now prevents double polling
|
|
998
|
+
start_polling: startPolling,
|
|
905
999
|
stop_polling: () => handler.cleanup(),
|
|
906
1000
|
cancel: () => {
|
|
907
1001
|
handler.cancel();
|
|
908
1002
|
handler.cleanup();
|
|
1003
|
+
// Reject the credential promise if polling hasn't started
|
|
1004
|
+
if (!pollingStarted && credentialReject) {
|
|
1005
|
+
credentialReject(this.createError(PhoneAuthErrorCode.USER_DENIED, 'Link authentication cancelled by user'));
|
|
1006
|
+
}
|
|
909
1007
|
},
|
|
910
|
-
|
|
1008
|
+
// This will be replaced with a getter
|
|
1009
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
911
1010
|
};
|
|
1011
|
+
// Define is_polling as a getter that returns current state
|
|
1012
|
+
Object.defineProperty(response, 'is_polling', {
|
|
1013
|
+
get() {
|
|
1014
|
+
return handler.isPolling();
|
|
1015
|
+
},
|
|
1016
|
+
enumerable: true,
|
|
1017
|
+
configurable: true
|
|
1018
|
+
});
|
|
1019
|
+
return response;
|
|
912
1020
|
}
|
|
913
1021
|
else {
|
|
914
|
-
// Standard mode -
|
|
1022
|
+
// Standard mode - start polling immediately and wait for completion
|
|
1023
|
+
const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
1024
|
+
if (result.authenticated && result.credential) {
|
|
1025
|
+
return {
|
|
1026
|
+
credential: result.credential,
|
|
1027
|
+
session: plainResponse.session,
|
|
1028
|
+
authenticated: true
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
throw this.createError(PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
915
1035
|
// Wait for credential and return in standard format
|
|
916
1036
|
const credential = yield credentialPromise;
|
|
917
1037
|
const aggregatorId = this.config.aggregatorId || 'default';
|
|
@@ -17,7 +17,7 @@ export interface QRCodeData {
|
|
|
17
17
|
export interface DesktopAuthOptions {
|
|
18
18
|
/** Custom polling interval in milliseconds (overrides server-provided value) */
|
|
19
19
|
pollingInterval?: number;
|
|
20
|
-
/** Maximum polling attempts before timeout (default:
|
|
20
|
+
/** Maximum polling attempts before timeout (default: 30 = 1 minute with 2s interval) */
|
|
21
21
|
maxPollingAttempts?: number;
|
|
22
22
|
/** Custom polling endpoint (overrides backend-provided or uses configured endpoint) */
|
|
23
23
|
pollingEndpoint?: string;
|
|
@@ -56,6 +56,7 @@ export declare class DesktopHandler implements StrategyHandler {
|
|
|
56
56
|
private isCancelled;
|
|
57
57
|
private onCancel?;
|
|
58
58
|
private pollingReject?;
|
|
59
|
+
private isPollingInProgress;
|
|
59
60
|
/**
|
|
60
61
|
* Maps backend HTTP status codes to client status
|
|
61
62
|
* @param httpStatus HTTP status code from backend
|
|
@@ -76,6 +77,10 @@ export declare class DesktopHandler implements StrategyHandler {
|
|
|
76
77
|
* Stop polling
|
|
77
78
|
*/
|
|
78
79
|
private stopPolling;
|
|
80
|
+
/**
|
|
81
|
+
* Check if polling is currently active
|
|
82
|
+
*/
|
|
83
|
+
isPolling(): boolean;
|
|
79
84
|
/**
|
|
80
85
|
* Format response for backend processing
|
|
81
86
|
* Desktop strategy typically returns the credential from mobile authentication
|
|
@@ -98,7 +103,7 @@ export declare class DesktopHandler implements StrategyHandler {
|
|
|
98
103
|
/**
|
|
99
104
|
* Helper function to display QR code in a modal or inline
|
|
100
105
|
*/
|
|
101
|
-
export declare function createQRCodeDisplay(qrCodeData: string, options?: {
|
|
106
|
+
export declare function createQRCodeDisplay(qrCodeData: string | QRCodeData, options?: {
|
|
102
107
|
container?: HTMLElement;
|
|
103
108
|
size?: number;
|
|
104
109
|
title?: string;
|
|
@@ -106,8 +111,9 @@ export declare function createQRCodeDisplay(qrCodeData: string, options?: {
|
|
|
106
111
|
}): HTMLElement;
|
|
107
112
|
/**
|
|
108
113
|
* Helper function to create a modal for QR code display
|
|
114
|
+
* Supports both string QR codes and QRCodeData objects for dual-platform support
|
|
109
115
|
*/
|
|
110
|
-
export declare function showQRCodeModal(qrCodeData: string, options?: {
|
|
116
|
+
export declare function showQRCodeModal(qrCodeData: string | QRCodeData, options?: {
|
|
111
117
|
title?: string;
|
|
112
118
|
description?: string;
|
|
113
119
|
onClose?: () => void;
|
|
@@ -16,6 +16,7 @@ export class DesktopHandler {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
this.pollingAttempts = 0;
|
|
18
18
|
this.isCancelled = false;
|
|
19
|
+
this.isPollingInProgress = false;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* Maps backend HTTP status codes to client status
|
|
@@ -58,8 +59,8 @@ export class DesktopHandler {
|
|
|
58
59
|
const innerData = desktopData.data;
|
|
59
60
|
// Try to extract from inner data
|
|
60
61
|
// Support both single QR (qr_code_image) and dual-platform QR (ios/android)
|
|
61
|
-
if (innerData.ios_qr_image
|
|
62
|
-
// Dual-platform QR format
|
|
62
|
+
if (innerData.ios_qr_image && innerData.android_qr_image) {
|
|
63
|
+
// Dual-platform QR format - both must be present
|
|
63
64
|
qrCode = {
|
|
64
65
|
iosQRCode: innerData.ios_qr_image,
|
|
65
66
|
androidQRCode: innerData.android_qr_image,
|
|
@@ -67,6 +68,10 @@ export class DesktopHandler {
|
|
|
67
68
|
androidUrl: innerData.android_url
|
|
68
69
|
};
|
|
69
70
|
}
|
|
71
|
+
else if (innerData.ios_qr_image || innerData.android_qr_image) {
|
|
72
|
+
// Only one platform QR - use as single QR format
|
|
73
|
+
qrCode = innerData.ios_qr_image || innerData.android_qr_image;
|
|
74
|
+
}
|
|
70
75
|
else {
|
|
71
76
|
// Single QR format (legacy)
|
|
72
77
|
qrCode = innerData.qr_code_image || innerData.qr_code;
|
|
@@ -117,7 +122,7 @@ export class DesktopHandler {
|
|
|
117
122
|
console.log('[Desktop QR] Selected endpoint:', finalPollingEndpoint, 'from source:', endpointSource);
|
|
118
123
|
// Start polling for authentication status
|
|
119
124
|
const finalPollingInterval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || pollingInterval || 2000;
|
|
120
|
-
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) ||
|
|
125
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30; // Default to 1 minute
|
|
121
126
|
console.log(`[Desktop QR] Starting polling - endpoint source: ${endpointSource}, interval: ${finalPollingInterval}ms, max attempts: ${maxAttempts}`);
|
|
122
127
|
return this.startPolling(finalPollingEndpoint || '', // Pass empty string if undefined, will use fallback
|
|
123
128
|
sessionId, finalPollingInterval, maxAttempts, expiresIn || 300, // Default 5 minutes expiry
|
|
@@ -136,7 +141,12 @@ export class DesktopHandler {
|
|
|
136
141
|
// Store the reject function so we can call it from cancel()
|
|
137
142
|
this.pollingReject = reject;
|
|
138
143
|
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
// Skip if another poll is already in progress
|
|
145
|
+
if (this.isPollingInProgress) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
139
148
|
try {
|
|
149
|
+
this.isPollingInProgress = true;
|
|
140
150
|
// Check if cancelled
|
|
141
151
|
if (this.isCancelled) {
|
|
142
152
|
this.stopPolling();
|
|
@@ -360,6 +370,10 @@ export class DesktopHandler {
|
|
|
360
370
|
});
|
|
361
371
|
}
|
|
362
372
|
}
|
|
373
|
+
finally {
|
|
374
|
+
// Always clear the polling flag when done
|
|
375
|
+
this.isPollingInProgress = false;
|
|
376
|
+
}
|
|
363
377
|
});
|
|
364
378
|
// Start initial poll
|
|
365
379
|
poll();
|
|
@@ -378,8 +392,15 @@ export class DesktopHandler {
|
|
|
378
392
|
this.pollingIntervalId = undefined;
|
|
379
393
|
}
|
|
380
394
|
this.pollingAttempts = 0;
|
|
395
|
+
this.isPollingInProgress = false;
|
|
381
396
|
// Don't clear pollingReject here - it's needed by cancel()
|
|
382
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Check if polling is currently active
|
|
400
|
+
*/
|
|
401
|
+
isPolling() {
|
|
402
|
+
return this.pollingIntervalId !== undefined;
|
|
403
|
+
}
|
|
383
404
|
/**
|
|
384
405
|
* Format response for backend processing
|
|
385
406
|
* Desktop strategy typically returns the credential from mobile authentication
|
|
@@ -437,9 +458,30 @@ export class DesktopHandler {
|
|
|
437
458
|
export function createQRCodeDisplay(qrCodeData, options) {
|
|
438
459
|
const container = (options === null || options === void 0 ? void 0 : options.container) || document.createElement('div');
|
|
439
460
|
container.className = 'phone-auth-qr-container';
|
|
461
|
+
// Determine QR code to display
|
|
462
|
+
let qrCodeSrc;
|
|
463
|
+
if (typeof qrCodeData === 'string') {
|
|
464
|
+
// Simple string QR code
|
|
465
|
+
qrCodeSrc = qrCodeData;
|
|
466
|
+
}
|
|
467
|
+
else if (qrCodeData && typeof qrCodeData === 'object') {
|
|
468
|
+
// QRCodeData object - prefer iOS QR if available
|
|
469
|
+
if (qrCodeData.iosQRCode) {
|
|
470
|
+
qrCodeSrc = qrCodeData.iosQRCode;
|
|
471
|
+
}
|
|
472
|
+
else if (qrCodeData.androidQRCode) {
|
|
473
|
+
qrCodeSrc = qrCodeData.androidQRCode;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
throw new Error('Invalid QRCodeData: missing QR code images');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
throw new Error('Invalid qrCodeData: must be string or QRCodeData object');
|
|
481
|
+
}
|
|
440
482
|
// Create QR code image
|
|
441
483
|
const img = document.createElement('img');
|
|
442
|
-
img.src =
|
|
484
|
+
img.src = qrCodeSrc;
|
|
443
485
|
img.alt = 'QR Code for authentication';
|
|
444
486
|
img.style.width = `${(options === null || options === void 0 ? void 0 : options.size) || 256}px`;
|
|
445
487
|
img.style.height = `${(options === null || options === void 0 ? void 0 : options.size) || 256}px`;
|
|
@@ -467,6 +509,7 @@ export function createQRCodeDisplay(qrCodeData, options) {
|
|
|
467
509
|
}
|
|
468
510
|
/**
|
|
469
511
|
* Helper function to create a modal for QR code display
|
|
512
|
+
* Supports both string QR codes and QRCodeData objects for dual-platform support
|
|
470
513
|
*/
|
|
471
514
|
export function showQRCodeModal(qrCodeData, options) {
|
|
472
515
|
// Create modal overlay
|
|
@@ -8,7 +8,7 @@ import type { PrepareResponse } from '../types';
|
|
|
8
8
|
export interface LinkAuthOptions {
|
|
9
9
|
/** Fixed polling interval in milliseconds (default: 2000) */
|
|
10
10
|
pollingInterval?: number;
|
|
11
|
-
/** Maximum polling attempts before timeout (default:
|
|
11
|
+
/** Maximum polling attempts before timeout (default: 30 = 1 minute with 2s interval) */
|
|
12
12
|
maxPollingAttempts?: number;
|
|
13
13
|
/** Custom polling endpoint (overrides backend-provided or configured endpoint) */
|
|
14
14
|
pollingEndpoint?: string;
|
|
@@ -41,9 +41,11 @@ export interface LinkAuthResult {
|
|
|
41
41
|
}
|
|
42
42
|
export declare class LinkHandler implements StrategyHandler {
|
|
43
43
|
private pollingInterval?;
|
|
44
|
-
private
|
|
44
|
+
private isPollingActive;
|
|
45
45
|
private isCancelled;
|
|
46
46
|
private onCancel?;
|
|
47
|
+
private pollingReject?;
|
|
48
|
+
private isPollingInProgress;
|
|
47
49
|
/**
|
|
48
50
|
* Invoke link-based authentication
|
|
49
51
|
* Opens authentication app while keeping user on current page
|
|
@@ -74,6 +76,10 @@ export declare class LinkHandler implements StrategyHandler {
|
|
|
74
76
|
* Clean up resources (stop polling if active)
|
|
75
77
|
*/
|
|
76
78
|
cleanup(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Check if polling is currently active
|
|
81
|
+
*/
|
|
82
|
+
isPolling(): boolean;
|
|
77
83
|
/**
|
|
78
84
|
* Cancel the ongoing authentication
|
|
79
85
|
*/
|