@glideidentity/web-client-sdk 4.4.8-beta.3 → 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.
@@ -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
- // Validate use case requirements first
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 && !((_a = options.options) === null || _a === void 0 ? void 0 : _a.parent_session_id)) {
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
- // If only parent_session_id is provided, use_case is optional (will be inherited)
367
- if (((_b = options.options) === null || _b === void 0 ? void 0 : _b.parent_session_id) && !options.phone_number && !options.plmn && !options.use_case) {
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 only - all fields will be inherited from parent session');
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
- // Required fields
381
+ // Include use_case if provided (optional when parent_session_id is given)
386
382
  use_case: options.use_case,
387
- // One of these is required
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 (_c) {
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
- let credentialPromise;
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
- credentialPromise = Promise.resolve({
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
- credentialPromise = Promise.reject(error);
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
- return {
706
+ const response = {
707
707
  strategy: 'ts43',
708
708
  session: plainResponse.session,
709
- credential: credentialPromise,
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
- credentialPromise = Promise.resolve({
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
- credentialPromise = Promise.reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
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 credentialPromise;
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 || 150, onQRCodeReady: undefined, onStatusUpdate: undefined });
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
- const credentialPromise = showModal ? startPolling() : Promise.resolve({
824
- credential: '',
825
- session: plainResponse.session,
826
- authenticated: false
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
- return {
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: startPolling,
835
- stop_polling: () => {
836
- handler.cleanup();
837
- if (modal)
838
- modal.close();
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,62 @@ 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 || 150,
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
- return {
976
+ // Extended mode - return control methods without starting polling immediately
977
+ let credentialResolve = null;
978
+ let credentialReject = null;
979
+ let pollingStarted = false;
980
+ let pollingPromise = null;
981
+ // Create a pending promise that will be resolved when polling completes
982
+ const credentialPromise = new Promise((resolve, reject) => {
983
+ credentialResolve = resolve;
984
+ credentialReject = reject;
985
+ });
986
+ // Function to start polling (only once)
987
+ const startPolling = () => __awaiter(this, void 0, void 0, function* () {
988
+ if (pollingStarted) {
989
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.INVALID_SESSION_STATE, 'Polling has already been started');
990
+ }
991
+ pollingStarted = true;
992
+ try {
993
+ // Store the polling promise to ensure we use the same one
994
+ if (!pollingPromise) {
995
+ pollingPromise = handler.invoke(plainResponse, pollingOptions);
996
+ }
997
+ const result = yield pollingPromise;
998
+ if (result.authenticated && result.credential) {
999
+ const authCredential = {
1000
+ credential: result.credential,
1001
+ session: plainResponse.session,
1002
+ authenticated: true
1003
+ };
1004
+ if (credentialResolve) {
1005
+ credentialResolve(authCredential);
1006
+ }
1007
+ return authCredential;
1008
+ }
1009
+ else {
1010
+ const error = this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
1011
+ if (credentialReject) {
1012
+ credentialReject(error);
1013
+ }
1014
+ throw error;
1015
+ }
1016
+ }
1017
+ catch (err) {
1018
+ if (credentialReject) {
1019
+ credentialReject(err);
1020
+ }
1021
+ throw err;
1022
+ }
1023
+ });
1024
+ const response = {
933
1025
  strategy: 'link',
934
1026
  session: plainResponse.session,
935
1027
  credential: credentialPromise,
@@ -938,22 +1030,44 @@ class PhoneAuthClient {
938
1030
  },
939
1031
  // Re-open the app link
940
1032
  trigger: triggerLink,
941
- // Polling control
942
- start_polling: () => handler.invoke(plainResponse, pollingOptions).then(result => ({
943
- credential: result.credential || '',
944
- session: plainResponse.session,
945
- authenticated: result.authenticated
946
- })),
1033
+ // Polling control - now prevents double polling
1034
+ start_polling: startPolling,
947
1035
  stop_polling: () => handler.cleanup(),
948
1036
  cancel: () => {
949
1037
  handler.cancel();
950
1038
  handler.cleanup();
1039
+ // Reject the credential promise if polling hasn't started
1040
+ if (!pollingStarted && credentialReject) {
1041
+ credentialReject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Link authentication cancelled by user'));
1042
+ }
951
1043
  },
952
- is_polling: true
1044
+ // This will be replaced with a getter
1045
+ is_polling: false // Initial value, will be overridden by getter
953
1046
  };
1047
+ // Define is_polling as a getter that returns current state
1048
+ Object.defineProperty(response, 'is_polling', {
1049
+ get() {
1050
+ return handler.isPolling();
1051
+ },
1052
+ enumerable: true,
1053
+ configurable: true
1054
+ });
1055
+ return response;
954
1056
  }
955
1057
  else {
956
- // Standard mode - return credential when complete
1058
+ // Standard mode - start polling immediately and wait for completion
1059
+ const credentialPromise = handler.invoke(plainResponse, pollingOptions).then(result => {
1060
+ if (result.authenticated && result.credential) {
1061
+ return {
1062
+ credential: result.credential,
1063
+ session: plainResponse.session,
1064
+ authenticated: true
1065
+ };
1066
+ }
1067
+ else {
1068
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed');
1069
+ }
1070
+ });
957
1071
  // Wait for credential and return in standard format
958
1072
  const credential = yield credentialPromise;
959
1073
  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: 150 = 5 minutes with 2s interval) */
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 || innerData.android_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) || 150; // Default to 5 minutes
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 = qrCodeData;
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: 150 = 5 minutes with 2s interval) */
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 isPolling;
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
  */