@glideidentity/web-client-sdk 4.4.8-beta.3 → 4.4.9
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 +189 -73
- 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 +46 -10
- 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 +189 -73
- 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 +46 -10
- 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
|
@@ -206,6 +206,10 @@ class PhoneAuthClient {
|
|
|
206
206
|
}
|
|
207
207
|
// Step 3: Process the response through appropriate endpoint
|
|
208
208
|
const credential = credentialResponse;
|
|
209
|
+
// Validate use_case is provided for endpoint selection
|
|
210
|
+
if (!options.use_case) {
|
|
211
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
212
|
+
}
|
|
209
213
|
const result = options.use_case === API.USE_CASE.GET_PHONE_NUMBER
|
|
210
214
|
? yield this.getPhoneNumber(credential, preparedRequest.session)
|
|
211
215
|
: yield this.verifyPhoneNumber(credential, preparedRequest.session);
|
|
@@ -314,16 +318,7 @@ class PhoneAuthClient {
|
|
|
314
318
|
*/
|
|
315
319
|
preparePhoneRequest(options) {
|
|
316
320
|
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
-
|
|
318
|
-
// const useCaseValidation = validateUseCaseRequirements(options.use_case, options.phone_number);
|
|
319
|
-
// if (!useCaseValidation.valid) {
|
|
320
|
-
// throw this.createError(
|
|
321
|
-
// PhoneAuthErrorCode.BAD_REQUEST,
|
|
322
|
-
// useCaseValidation.error!,
|
|
323
|
-
// { field: 'use_case' }
|
|
324
|
-
// );
|
|
325
|
-
// }
|
|
326
|
-
var _a, _b;
|
|
321
|
+
var _a, _b, _c;
|
|
327
322
|
// Validate phone number if provided
|
|
328
323
|
if (options.phone_number) {
|
|
329
324
|
const phoneValidation = (0, validation_utils_1.validatePhoneNumber)(options.phone_number);
|
|
@@ -345,8 +340,12 @@ class PhoneAuthClient {
|
|
|
345
340
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.BAD_REQUEST, consentValidation.error, { field: 'consent_data' });
|
|
346
341
|
}
|
|
347
342
|
}
|
|
343
|
+
// Validate use_case is provided (unless only parent_session_id is given)
|
|
344
|
+
if (!options.use_case && !(((_a = options.options) === null || _a === void 0 ? void 0 : _a.parent_session_id) && !options.phone_number && !options.plmn)) {
|
|
345
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
346
|
+
}
|
|
348
347
|
// Validate required parameters based on use case
|
|
349
|
-
if (!options.phone_number && !options.plmn && !((
|
|
348
|
+
if (!options.phone_number && !options.plmn && !((_b = options.options) === null || _b === void 0 ? void 0 : _b.parent_session_id)) {
|
|
350
349
|
// Provide specific error message based on use case
|
|
351
350
|
if (options.use_case === API.USE_CASE.GET_PHONE_NUMBER) {
|
|
352
351
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'PLMN (MCC/MNC) is required for GetPhoneNumber. Please provide carrier network information.', { field: 'plmn', useCase: 'GetPhoneNumber' });
|
|
@@ -354,19 +353,15 @@ class PhoneAuthClient {
|
|
|
354
353
|
else if (options.use_case === API.USE_CASE.VERIFY_PHONE_NUMBER) {
|
|
355
354
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Phone number is required for VerifyPhoneNumber', { field: 'phoneNumber', useCase: 'VerifyPhoneNumber' });
|
|
356
355
|
}
|
|
357
|
-
else if (!options.use_case) {
|
|
358
|
-
// If no use case, that's an error
|
|
359
|
-
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'use_case is required', { field: 'use_case' });
|
|
360
|
-
}
|
|
361
356
|
else {
|
|
362
357
|
// Fallback for other use cases
|
|
363
358
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.MISSING_PARAMETERS, 'Either phone number or PLMN (MCC/MNC) must be provided', { field: 'phoneNumber,plmn' });
|
|
364
359
|
}
|
|
365
360
|
}
|
|
366
|
-
//
|
|
367
|
-
if (((
|
|
361
|
+
// Log parent session usage
|
|
362
|
+
if (((_c = options.options) === null || _c === void 0 ? void 0 : _c.parent_session_id) && !options.phone_number && !options.plmn) {
|
|
368
363
|
if (this.debug) {
|
|
369
|
-
console.log('[PhoneAuth] Using parent_session_id
|
|
364
|
+
console.log('[PhoneAuth] Using parent_session_id: %s, use_case: %s', options.options.parent_session_id, options.use_case || 'not provided');
|
|
370
365
|
}
|
|
371
366
|
}
|
|
372
367
|
const requestId = `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -381,11 +376,13 @@ class PhoneAuthClient {
|
|
|
381
376
|
throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, 'Failed to generate valid nonce', { field: 'nonce' });
|
|
382
377
|
}
|
|
383
378
|
// Build properly typed request body according to API specification
|
|
379
|
+
// Be permissive - backend will ignore extra fields if not needed
|
|
384
380
|
const requestBody = {
|
|
385
|
-
//
|
|
381
|
+
// Include use_case if provided (optional when parent_session_id is given)
|
|
386
382
|
use_case: options.use_case,
|
|
387
|
-
//
|
|
383
|
+
// Include phone_number if provided (backend ignores if not needed)
|
|
388
384
|
phone_number: options.phone_number,
|
|
385
|
+
// Include PLMN if provided
|
|
389
386
|
plmn: options.plmn ? {
|
|
390
387
|
mcc: options.plmn.mcc,
|
|
391
388
|
mnc: options.plmn.mnc
|
|
@@ -417,7 +414,7 @@ class PhoneAuthClient {
|
|
|
417
414
|
// Always include the HTTP status from the response
|
|
418
415
|
errorDetails = Object.assign(Object.assign({}, errorDetails), { status: response.status });
|
|
419
416
|
}
|
|
420
|
-
catch (
|
|
417
|
+
catch (_d) {
|
|
421
418
|
// If JSON parsing fails, use status text
|
|
422
419
|
errorDetails = { status: response.status, statusText: response.statusText };
|
|
423
420
|
}
|
|
@@ -685,12 +682,15 @@ class PhoneAuthClient {
|
|
|
685
682
|
});
|
|
686
683
|
// TS43 always auto-triggers (no SDK UI ever)
|
|
687
684
|
// The Digital Credentials API provides its own OS-level UI
|
|
688
|
-
|
|
685
|
+
// Use a wrapper object to allow updating the promise reference
|
|
686
|
+
const credentialWrapper = {
|
|
687
|
+
promise: null
|
|
688
|
+
};
|
|
689
689
|
try {
|
|
690
690
|
// Always try to trigger immediately
|
|
691
691
|
const vpToken = yield enhancedTriggerTS43();
|
|
692
692
|
// Convert to AuthCredential format
|
|
693
|
-
|
|
693
|
+
credentialWrapper.promise = Promise.resolve({
|
|
694
694
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
695
695
|
session: plainResponse.session,
|
|
696
696
|
authenticated: true
|
|
@@ -698,36 +698,46 @@ class PhoneAuthClient {
|
|
|
698
698
|
}
|
|
699
699
|
catch (error) {
|
|
700
700
|
// If auto-trigger fails, create a rejected promise
|
|
701
|
-
|
|
701
|
+
credentialWrapper.promise = Promise.reject(error);
|
|
702
702
|
}
|
|
703
703
|
// Handle based on execution mode
|
|
704
704
|
if (executionMode === 'extended') {
|
|
705
705
|
// Extended mode - return control methods
|
|
706
|
-
|
|
706
|
+
const response = {
|
|
707
707
|
strategy: 'ts43',
|
|
708
708
|
session: plainResponse.session,
|
|
709
|
-
credential:
|
|
709
|
+
credential: credentialWrapper.promise, // Initial value
|
|
710
710
|
// Re-trigger credential request
|
|
711
711
|
trigger: () => __awaiter(this, void 0, void 0, function* () {
|
|
712
712
|
const vpToken = yield enhancedTriggerTS43();
|
|
713
|
-
// Update the credential promise
|
|
714
|
-
|
|
713
|
+
// Update the credential promise in the wrapper
|
|
714
|
+
credentialWrapper.promise = Promise.resolve({
|
|
715
715
|
credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
|
|
716
716
|
session: plainResponse.session,
|
|
717
717
|
authenticated: true
|
|
718
718
|
});
|
|
719
|
+
// Return void as per interface
|
|
719
720
|
}),
|
|
720
721
|
cancel: () => {
|
|
721
722
|
// TS43 doesn't have a way to cancel once triggered
|
|
722
723
|
// but we can reject the promise
|
|
723
|
-
|
|
724
|
+
credentialWrapper.promise = Promise.reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
|
|
724
725
|
}
|
|
725
726
|
};
|
|
727
|
+
// Define credential as a getter that always returns the current promise
|
|
728
|
+
Object.defineProperty(response, 'credential', {
|
|
729
|
+
get() {
|
|
730
|
+
return credentialWrapper.promise;
|
|
731
|
+
},
|
|
732
|
+
enumerable: true,
|
|
733
|
+
configurable: true
|
|
734
|
+
});
|
|
735
|
+
return response;
|
|
726
736
|
}
|
|
727
737
|
else {
|
|
728
738
|
// Standard mode - just return credential
|
|
729
739
|
// Wait for and return the credential
|
|
730
|
-
const credential = yield
|
|
740
|
+
const credential = yield credentialWrapper.promise;
|
|
731
741
|
// Return in standard format
|
|
732
742
|
return {
|
|
733
743
|
[plainResponse.session.session_key]: credential.credential
|
|
@@ -757,7 +767,7 @@ class PhoneAuthClient {
|
|
|
757
767
|
const pollingEndpointToUse = (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) ||
|
|
758
768
|
(desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingEndpoint) ||
|
|
759
769
|
((_m = this.config.endpoints) === null || _m === void 0 ? void 0 : _m.polling);
|
|
760
|
-
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 ||
|
|
770
|
+
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 });
|
|
761
771
|
// Decide whether to show modal based on preventDefaultUI
|
|
762
772
|
const showModal = !preventDefaultUI;
|
|
763
773
|
let modal;
|
|
@@ -820,31 +830,79 @@ class PhoneAuthClient {
|
|
|
820
830
|
// Handle based on execution mode
|
|
821
831
|
if (executionMode === 'extended') {
|
|
822
832
|
// Extended mode - return control methods
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
833
|
+
let credentialResolve = null;
|
|
834
|
+
let credentialReject = null;
|
|
835
|
+
let pollingStarted = false;
|
|
836
|
+
// Create a promise that will remain pending until polling starts
|
|
837
|
+
const credentialPromise = new Promise((resolve, reject) => {
|
|
838
|
+
credentialResolve = resolve;
|
|
839
|
+
credentialReject = reject;
|
|
840
|
+
// If modal is shown, start polling immediately
|
|
841
|
+
if (showModal) {
|
|
842
|
+
pollingStarted = true;
|
|
843
|
+
startPolling()
|
|
844
|
+
.then(resolve)
|
|
845
|
+
.catch(reject);
|
|
846
|
+
}
|
|
847
|
+
// Otherwise, promise remains pending until start_polling() is called
|
|
827
848
|
});
|
|
828
|
-
|
|
849
|
+
// Create wrapped functions
|
|
850
|
+
const wrappedStartPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
851
|
+
if (pollingStarted) {
|
|
852
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_SESSION_STATE, 'Polling has already been started');
|
|
853
|
+
}
|
|
854
|
+
pollingStarted = true;
|
|
855
|
+
try {
|
|
856
|
+
const result = yield startPolling();
|
|
857
|
+
if (credentialResolve) {
|
|
858
|
+
credentialResolve(result);
|
|
859
|
+
}
|
|
860
|
+
return result;
|
|
861
|
+
}
|
|
862
|
+
catch (err) {
|
|
863
|
+
if (credentialReject) {
|
|
864
|
+
credentialReject(err);
|
|
865
|
+
}
|
|
866
|
+
throw err;
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
const wrappedStopPolling = () => {
|
|
870
|
+
handler.cleanup();
|
|
871
|
+
if (modal)
|
|
872
|
+
modal.close();
|
|
873
|
+
};
|
|
874
|
+
const wrappedCancel = () => {
|
|
875
|
+
handler.cancel();
|
|
876
|
+
handler.cleanup();
|
|
877
|
+
if (modal)
|
|
878
|
+
modal.close();
|
|
879
|
+
// Reject the credential promise if it's still pending
|
|
880
|
+
if (!pollingStarted && credentialReject) {
|
|
881
|
+
credentialReject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Desktop authentication cancelled by user'));
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
// Create the response object with a getter for is_polling
|
|
885
|
+
const response = {
|
|
829
886
|
strategy: 'desktop',
|
|
830
887
|
session: plainResponse.session,
|
|
831
888
|
credential: credentialPromise,
|
|
832
889
|
qr_code_data: qrCodeDataSnakeCase,
|
|
833
890
|
modal_ref: modalRef,
|
|
834
|
-
start_polling:
|
|
835
|
-
stop_polling:
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
},
|
|
840
|
-
cancel: () => {
|
|
841
|
-
handler.cancel();
|
|
842
|
-
handler.cleanup();
|
|
843
|
-
if (modal)
|
|
844
|
-
modal.close();
|
|
845
|
-
},
|
|
846
|
-
is_polling: showModal // If modal shown, polling already started
|
|
891
|
+
start_polling: wrappedStartPolling,
|
|
892
|
+
stop_polling: wrappedStopPolling,
|
|
893
|
+
cancel: wrappedCancel,
|
|
894
|
+
// This will be replaced with a getter
|
|
895
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
847
896
|
};
|
|
897
|
+
// Define is_polling as a getter that returns current state
|
|
898
|
+
Object.defineProperty(response, 'is_polling', {
|
|
899
|
+
get() {
|
|
900
|
+
return handler.isPolling();
|
|
901
|
+
},
|
|
902
|
+
enumerable: true,
|
|
903
|
+
configurable: true
|
|
904
|
+
});
|
|
905
|
+
return response;
|
|
848
906
|
}
|
|
849
907
|
else {
|
|
850
908
|
// Standard mode - return credential when complete
|
|
@@ -908,28 +966,66 @@ class PhoneAuthClient {
|
|
|
908
966
|
const pollingOptions = {
|
|
909
967
|
pollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_q = this.config.endpoints) === null || _q === void 0 ? void 0 : _q.polling),
|
|
910
968
|
pollingInterval: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingInterval) || this.config.pollingInterval || 2000,
|
|
911
|
-
maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || this.config.maxPollingAttempts ||
|
|
969
|
+
maxPollingAttempts: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 30,
|
|
912
970
|
onLinkOpened: undefined,
|
|
913
971
|
onStatusUpdate: undefined
|
|
914
972
|
};
|
|
915
973
|
console.log('[PhoneAuth Client] Final Link polling options:', pollingOptions);
|
|
916
|
-
// Create credential promise
|
|
917
|
-
const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
918
|
-
if (result.authenticated && result.credential) {
|
|
919
|
-
return {
|
|
920
|
-
credential: result.credential,
|
|
921
|
-
session: plainResponse.session,
|
|
922
|
-
authenticated: true
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
927
|
-
}
|
|
928
|
-
});
|
|
929
974
|
// Handle based on execution mode
|
|
930
975
|
if (executionMode === 'extended') {
|
|
931
|
-
// Extended mode - return control methods
|
|
932
|
-
|
|
976
|
+
// Extended mode - return control methods and start polling immediately
|
|
977
|
+
let pollingStarted = false;
|
|
978
|
+
let pollingPromise = null;
|
|
979
|
+
// Start polling immediately (Link always polls automatically unlike Desktop)
|
|
980
|
+
pollingPromise = handler.invoke(plainResponse, pollingOptions);
|
|
981
|
+
pollingStarted = true;
|
|
982
|
+
// Create credential promise from the polling result
|
|
983
|
+
const credentialPromise = pollingPromise.then(result => {
|
|
984
|
+
if (result.authenticated && result.credential) {
|
|
985
|
+
return {
|
|
986
|
+
credential: result.credential,
|
|
987
|
+
session: plainResponse.session,
|
|
988
|
+
authenticated: true
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
// Function to restart polling (for retry scenarios)
|
|
996
|
+
const startPolling = () => __awaiter(this, void 0, void 0, function* () {
|
|
997
|
+
if (!pollingStarted) {
|
|
998
|
+
// This is here for API consistency, but for Link it's already polling
|
|
999
|
+
pollingStarted = true;
|
|
1000
|
+
if (!pollingPromise) {
|
|
1001
|
+
pollingPromise = handler.invoke(plainResponse, pollingOptions);
|
|
1002
|
+
}
|
|
1003
|
+
const result = yield pollingPromise;
|
|
1004
|
+
if (result.authenticated && result.credential) {
|
|
1005
|
+
return {
|
|
1006
|
+
credential: result.credential,
|
|
1007
|
+
session: plainResponse.session,
|
|
1008
|
+
authenticated: true
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
// If already polling, just return the existing promise result
|
|
1016
|
+
const result = yield pollingPromise;
|
|
1017
|
+
if (result.authenticated && result.credential) {
|
|
1018
|
+
return {
|
|
1019
|
+
credential: result.credential,
|
|
1020
|
+
session: plainResponse.session,
|
|
1021
|
+
authenticated: true
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
const response = {
|
|
933
1029
|
strategy: 'link',
|
|
934
1030
|
session: plainResponse.session,
|
|
935
1031
|
credential: credentialPromise,
|
|
@@ -938,22 +1034,42 @@ class PhoneAuthClient {
|
|
|
938
1034
|
},
|
|
939
1035
|
// Re-open the app link
|
|
940
1036
|
trigger: triggerLink,
|
|
941
|
-
// Polling control
|
|
942
|
-
start_polling:
|
|
943
|
-
credential: result.credential || '',
|
|
944
|
-
session: plainResponse.session,
|
|
945
|
-
authenticated: result.authenticated
|
|
946
|
-
})),
|
|
1037
|
+
// Polling control - now prevents double polling
|
|
1038
|
+
start_polling: startPolling,
|
|
947
1039
|
stop_polling: () => handler.cleanup(),
|
|
948
1040
|
cancel: () => {
|
|
949
1041
|
handler.cancel();
|
|
950
1042
|
handler.cleanup();
|
|
1043
|
+
// Note: For Link, polling is already started, so the promise
|
|
1044
|
+
// will be rejected by the handler.cancel() call
|
|
951
1045
|
},
|
|
952
|
-
|
|
1046
|
+
// This will be replaced with a getter
|
|
1047
|
+
is_polling: false // Initial value, will be overridden by getter
|
|
953
1048
|
};
|
|
1049
|
+
// Define is_polling as a getter that returns current state
|
|
1050
|
+
Object.defineProperty(response, 'is_polling', {
|
|
1051
|
+
get() {
|
|
1052
|
+
return handler.isPolling();
|
|
1053
|
+
},
|
|
1054
|
+
enumerable: true,
|
|
1055
|
+
configurable: true
|
|
1056
|
+
});
|
|
1057
|
+
return response;
|
|
954
1058
|
}
|
|
955
1059
|
else {
|
|
956
|
-
// Standard mode -
|
|
1060
|
+
// Standard mode - start polling immediately and wait for completion
|
|
1061
|
+
const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
|
|
1062
|
+
if (result.authenticated && result.credential) {
|
|
1063
|
+
return {
|
|
1064
|
+
credential: result.credential,
|
|
1065
|
+
session: plainResponse.session,
|
|
1066
|
+
authenticated: true
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
957
1073
|
// Wait for credential and return in standard format
|
|
958
1074
|
const credential = yield credentialPromise;
|
|
959
1075
|
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;
|
|
@@ -21,6 +21,7 @@ class DesktopHandler {
|
|
|
21
21
|
constructor() {
|
|
22
22
|
this.pollingAttempts = 0;
|
|
23
23
|
this.isCancelled = false;
|
|
24
|
+
this.isPollingInProgress = false;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Maps backend HTTP status codes to client status
|
|
@@ -63,8 +64,8 @@ class DesktopHandler {
|
|
|
63
64
|
const innerData = desktopData.data;
|
|
64
65
|
// Try to extract from inner data
|
|
65
66
|
// Support both single QR (qr_code_image) and dual-platform QR (ios/android)
|
|
66
|
-
if (innerData.ios_qr_image
|
|
67
|
-
// Dual-platform QR format
|
|
67
|
+
if (innerData.ios_qr_image && innerData.android_qr_image) {
|
|
68
|
+
// Dual-platform QR format - both must be present
|
|
68
69
|
qrCode = {
|
|
69
70
|
iosQRCode: innerData.ios_qr_image,
|
|
70
71
|
androidQRCode: innerData.android_qr_image,
|
|
@@ -72,6 +73,10 @@ class DesktopHandler {
|
|
|
72
73
|
androidUrl: innerData.android_url
|
|
73
74
|
};
|
|
74
75
|
}
|
|
76
|
+
else if (innerData.ios_qr_image || innerData.android_qr_image) {
|
|
77
|
+
// Only one platform QR - use as single QR format
|
|
78
|
+
qrCode = innerData.ios_qr_image || innerData.android_qr_image;
|
|
79
|
+
}
|
|
75
80
|
else {
|
|
76
81
|
// Single QR format (legacy)
|
|
77
82
|
qrCode = innerData.qr_code_image || innerData.qr_code;
|
|
@@ -122,7 +127,7 @@ class DesktopHandler {
|
|
|
122
127
|
console.log('[Desktop QR] Selected endpoint:', finalPollingEndpoint, 'from source:', endpointSource);
|
|
123
128
|
// Start polling for authentication status
|
|
124
129
|
const finalPollingInterval = (options === null || options === void 0 ? void 0 : options.pollingInterval) || pollingInterval || 2000;
|
|
125
|
-
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) ||
|
|
130
|
+
const maxAttempts = (options === null || options === void 0 ? void 0 : options.maxPollingAttempts) || 30; // Default to 1 minute
|
|
126
131
|
console.log(`[Desktop QR] Starting polling - endpoint source: ${endpointSource}, interval: ${finalPollingInterval}ms, max attempts: ${maxAttempts}`);
|
|
127
132
|
return this.startPolling(finalPollingEndpoint || '', // Pass empty string if undefined, will use fallback
|
|
128
133
|
sessionId, finalPollingInterval, maxAttempts, expiresIn || 300, // Default 5 minutes expiry
|
|
@@ -141,7 +146,12 @@ class DesktopHandler {
|
|
|
141
146
|
// Store the reject function so we can call it from cancel()
|
|
142
147
|
this.pollingReject = reject;
|
|
143
148
|
const poll = () => __awaiter(this, void 0, void 0, function* () {
|
|
149
|
+
// Skip if another poll is already in progress
|
|
150
|
+
if (this.isPollingInProgress) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
144
153
|
try {
|
|
154
|
+
this.isPollingInProgress = true;
|
|
145
155
|
// Check if cancelled
|
|
146
156
|
if (this.isCancelled) {
|
|
147
157
|
this.stopPolling();
|
|
@@ -365,6 +375,10 @@ class DesktopHandler {
|
|
|
365
375
|
});
|
|
366
376
|
}
|
|
367
377
|
}
|
|
378
|
+
finally {
|
|
379
|
+
// Always clear the polling flag when done
|
|
380
|
+
this.isPollingInProgress = false;
|
|
381
|
+
}
|
|
368
382
|
});
|
|
369
383
|
// Start initial poll
|
|
370
384
|
poll();
|
|
@@ -383,8 +397,15 @@ class DesktopHandler {
|
|
|
383
397
|
this.pollingIntervalId = undefined;
|
|
384
398
|
}
|
|
385
399
|
this.pollingAttempts = 0;
|
|
400
|
+
this.isPollingInProgress = false;
|
|
386
401
|
// Don't clear pollingReject here - it's needed by cancel()
|
|
387
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Check if polling is currently active
|
|
405
|
+
*/
|
|
406
|
+
isPolling() {
|
|
407
|
+
return this.pollingIntervalId !== undefined;
|
|
408
|
+
}
|
|
388
409
|
/**
|
|
389
410
|
* Format response for backend processing
|
|
390
411
|
* Desktop strategy typically returns the credential from mobile authentication
|
|
@@ -443,9 +464,30 @@ exports.DesktopHandler = DesktopHandler;
|
|
|
443
464
|
function createQRCodeDisplay(qrCodeData, options) {
|
|
444
465
|
const container = (options === null || options === void 0 ? void 0 : options.container) || document.createElement('div');
|
|
445
466
|
container.className = 'phone-auth-qr-container';
|
|
467
|
+
// Determine QR code to display
|
|
468
|
+
let qrCodeSrc;
|
|
469
|
+
if (typeof qrCodeData === 'string') {
|
|
470
|
+
// Simple string QR code
|
|
471
|
+
qrCodeSrc = qrCodeData;
|
|
472
|
+
}
|
|
473
|
+
else if (qrCodeData && typeof qrCodeData === 'object') {
|
|
474
|
+
// QRCodeData object - prefer iOS QR if available
|
|
475
|
+
if (qrCodeData.iosQRCode) {
|
|
476
|
+
qrCodeSrc = qrCodeData.iosQRCode;
|
|
477
|
+
}
|
|
478
|
+
else if (qrCodeData.androidQRCode) {
|
|
479
|
+
qrCodeSrc = qrCodeData.androidQRCode;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
throw new Error('Invalid QRCodeData: missing QR code images');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
throw new Error('Invalid qrCodeData: must be string or QRCodeData object');
|
|
487
|
+
}
|
|
446
488
|
// Create QR code image
|
|
447
489
|
const img = document.createElement('img');
|
|
448
|
-
img.src =
|
|
490
|
+
img.src = qrCodeSrc;
|
|
449
491
|
img.alt = 'QR Code for authentication';
|
|
450
492
|
img.style.width = `${(options === null || options === void 0 ? void 0 : options.size) || 256}px`;
|
|
451
493
|
img.style.height = `${(options === null || options === void 0 ? void 0 : options.size) || 256}px`;
|
|
@@ -473,6 +515,7 @@ function createQRCodeDisplay(qrCodeData, options) {
|
|
|
473
515
|
}
|
|
474
516
|
/**
|
|
475
517
|
* Helper function to create a modal for QR code display
|
|
518
|
+
* Supports both string QR codes and QRCodeData objects for dual-platform support
|
|
476
519
|
*/
|
|
477
520
|
function showQRCodeModal(qrCodeData, options) {
|
|
478
521
|
// 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
|
*/
|