@glideidentity/web-client-sdk 4.4.8-beta.1 → 4.4.8-beta.3

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.
Files changed (36) hide show
  1. package/dist/adapters/react/usePhoneAuth.d.ts +1 -1
  2. package/dist/adapters/vue/useClient.d.ts +3 -3
  3. package/dist/adapters/vue/usePhoneAuth.d.ts +1 -1
  4. package/dist/browser/web-client-sdk.min.js +1 -1
  5. package/dist/core/phone-auth/api-types.d.ts +112 -27
  6. package/dist/core/phone-auth/client.d.ts +13 -11
  7. package/dist/core/phone-auth/client.js +263 -248
  8. package/dist/core/phone-auth/index.d.ts +1 -1
  9. package/dist/core/phone-auth/index.js +7 -2
  10. package/dist/core/phone-auth/strategies/desktop.d.ts +1 -0
  11. package/dist/core/phone-auth/strategies/desktop.js +64 -18
  12. package/dist/core/phone-auth/strategies/link.js +97 -5
  13. package/dist/core/phone-auth/type-guards.d.ts +61 -43
  14. package/dist/core/phone-auth/type-guards.js +82 -44
  15. package/dist/core/phone-auth/ui/modal.js +14 -1
  16. package/dist/core/version.js +1 -1
  17. package/dist/esm/adapters/react/usePhoneAuth.d.ts +1 -1
  18. package/dist/esm/adapters/vue/useClient.d.ts +3 -3
  19. package/dist/esm/adapters/vue/usePhoneAuth.d.ts +1 -1
  20. package/dist/esm/core/phone-auth/api-types.d.ts +112 -27
  21. package/dist/esm/core/phone-auth/client.d.ts +13 -11
  22. package/dist/esm/core/phone-auth/client.js +263 -248
  23. package/dist/esm/core/phone-auth/index.d.ts +1 -1
  24. package/dist/esm/core/phone-auth/index.js +3 -1
  25. package/dist/esm/core/phone-auth/strategies/desktop.d.ts +1 -0
  26. package/dist/esm/core/phone-auth/strategies/desktop.js +64 -18
  27. package/dist/esm/core/phone-auth/strategies/link.js +97 -5
  28. package/dist/esm/core/phone-auth/type-guards.d.ts +61 -43
  29. package/dist/esm/core/phone-auth/type-guards.js +76 -41
  30. package/dist/esm/core/phone-auth/ui/modal.js +14 -1
  31. package/dist/esm/core/version.js +1 -1
  32. package/dist/esm/index.d.ts +2 -2
  33. package/dist/esm/index.js +3 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +7 -2
  36. package/package.json +1 -1
@@ -54,7 +54,7 @@ const link_1 = require("./strategies/link");
54
54
  const modal_1 = require("./ui/modal");
55
55
  class PhoneAuthClient {
56
56
  constructor(config = {}) {
57
- var _a, _b, _c;
57
+ var _a, _b, _c, _d;
58
58
  this.crossDeviceActive = false;
59
59
  this.retryCount = 0;
60
60
  this.sessionCache = new Map();
@@ -64,7 +64,8 @@ class PhoneAuthClient {
64
64
  this.config = {
65
65
  endpoints: {
66
66
  prepare: ((_a = config.endpoints) === null || _a === void 0 ? void 0 : _a.prepare) || '/api/magic-auth/prepare',
67
- process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process'
67
+ process: ((_b = config.endpoints) === null || _b === void 0 ? void 0 : _b.process) || '/api/magic-auth/process',
68
+ polling: (_c = config.endpoints) === null || _c === void 0 ? void 0 : _c.polling // Pass through the polling endpoint
68
69
  },
69
70
  timeout: config.timeout || 30000,
70
71
  pollingInterval: config.pollingInterval || 2000, // Default 2 seconds
@@ -87,7 +88,7 @@ class PhoneAuthClient {
87
88
  custom: config.logger
88
89
  });
89
90
  // Initialize developer tools if configured
90
- if (((_c = config.devtools) === null || _c === void 0 ? void 0 : _c.showMobileConsole) && typeof window !== 'undefined') {
91
+ if (((_d = config.devtools) === null || _d === void 0 ? void 0 : _d.showMobileConsole) && typeof window !== 'undefined') {
91
92
  Promise.resolve().then(() => __importStar(require('./ui/mobile-debug-console'))).then(({ MobileDebugConsole }) => {
92
93
  MobileDebugConsole.init();
93
94
  console.log('[PhoneAuth] Mobile debug console enabled');
@@ -487,23 +488,25 @@ class PhoneAuthClient {
487
488
  * });
488
489
  * ```
489
490
  *
490
- * @example Headless Mode (returns data)
491
+ * @example Extended Mode (returns control methods)
491
492
  * ```typescript
492
- * // Get raw data without showing UI
493
+ * // Get control methods for custom implementation
493
494
  * const result = await phoneAuth.invokeSecurePrompt(prepareResult, {
494
- * headless: true
495
+ * executionMode: 'extended',
496
+ * preventDefaultUI: true // Desktop: no modal
495
497
  * });
496
498
  *
497
- * if (result.strategy === 'link') {
498
- * // Open URL yourself
499
- * window.open(result.url);
500
- * await result.pollingPromise; // Wait for completion
499
+ * if (result.strategy === 'desktop') {
500
+ * // Show custom QR UI
501
+ * showCustomQR(result.qr_code_data);
502
+ * // Start polling
503
+ * await result.start_polling();
501
504
  * }
502
505
  * ```
503
506
  *
504
507
  * @param prepareResponse - Response from prepare() with strategy and data
505
- * @param options - Control UI behavior or enable headless mode
506
- * @returns Credential or HeadlessResult based on mode
508
+ * @param options - Control UI behavior and response type
509
+ * @returns Credential or ExtendedResponse based on executionMode
507
510
  */
508
511
  invokeSecurePrompt(prepareResponse, options) {
509
512
  return __awaiter(this, void 0, void 0, function* () {
@@ -511,7 +514,7 @@ class PhoneAuthClient {
511
514
  // This ensures we work with plain objects for browser APIs
512
515
  // Vue's reactivity system wraps objects in Proxies which can interfere
513
516
  // with browser APIs like Digital Credentials API that expect plain objects
514
- var _a, _b, _c;
517
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
515
518
  // Try structuredClone first (modern browsers), but catch errors and fallback to JSON method
516
519
  let plainResponse;
517
520
  try {
@@ -531,16 +534,17 @@ class PhoneAuthClient {
531
534
  console.log('[PhoneAuth] Session cache size:', this.sessionCache.size);
532
535
  console.log('[PhoneAuth] Retry count:', this.retryCount);
533
536
  console.log('[PhoneAuth] PrepareResponse received:', JSON.stringify(plainResponse, null, 2));
534
- // Check if we're in headless mode (new InvokeOptions format)
537
+ // Check if we're using new InvokeOptions format
535
538
  // Properly detect InvokeOptions by checking for any of its properties
536
- const invokeOptions = options && ('headless' in options ||
539
+ const invokeOptions = options && ('preventDefaultUI' in options ||
540
+ 'executionMode' in options ||
537
541
  'theme' in options ||
538
542
  'modalOptions' in options ||
539
543
  'callbacks' in options) ? options : undefined;
540
- // Smart defaults: Link and TS43 default to headless, Desktop defaults to UI mode
544
+ // Get configuration from options
541
545
  const strategy = plainResponse.authentication_strategy;
542
- const defaultHeadless = strategy === 'link' || strategy === 'ts43' ? true : false;
543
- const isHeadless = (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.headless) !== null && _a !== void 0 ? _a : defaultHeadless;
546
+ const preventDefaultUI = (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.preventDefaultUI) !== null && _a !== void 0 ? _a : false;
547
+ const executionMode = (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.executionMode) !== null && _b !== void 0 ? _b : 'standard';
544
548
  // DesktopAuthOptions is only used if not InvokeOptions
545
549
  const desktopOptions = options && !invokeOptions ? options : undefined;
546
550
  // Handle based on authentication strategy
@@ -679,270 +683,281 @@ class PhoneAuthClient {
679
683
  throw error;
680
684
  }
681
685
  });
682
- // Headless mode for TS43 still returns the trigger function for custom UI
683
- // This allows developers to control WHEN to trigger, but not HOW
684
- if (isHeadless) {
685
- // Auto-trigger for TS43 if enabled (may fail due to browser restrictions)
686
- if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
687
- try {
688
- // Try to trigger immediately if we have user gesture context
689
- const result = yield enhancedTriggerTS43();
690
- // If successful, return the result directly
691
- return result;
692
- }
693
- catch (error) {
694
- // If auto-trigger fails, continue to return HeadlessResult with trigger
695
- if (this.debug) {
696
- console.log('[TS43] Auto-trigger failed, returning trigger function:', error);
697
- }
698
- }
699
- }
700
- // Return trigger function for developers who want to control timing
686
+ // TS43 always auto-triggers (no SDK UI ever)
687
+ // The Digital Credentials API provides its own OS-level UI
688
+ let credentialPromise;
689
+ try {
690
+ // Always try to trigger immediately
691
+ const vpToken = yield enhancedTriggerTS43();
692
+ // Convert to AuthCredential format
693
+ credentialPromise = Promise.resolve({
694
+ credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
695
+ session: plainResponse.session,
696
+ authenticated: true
697
+ });
698
+ }
699
+ catch (error) {
700
+ // If auto-trigger fails, create a rejected promise
701
+ credentialPromise = Promise.reject(error);
702
+ }
703
+ // Handle based on execution mode
704
+ if (executionMode === 'extended') {
705
+ // Extended mode - return control methods
701
706
  return {
702
- strategy: API.AUTHENTICATION_STRATEGY.TS43,
703
- credentialRequest: secureCredentialRequest,
704
- trigger: enhancedTriggerTS43,
705
- session: plainResponse.session
707
+ strategy: 'ts43',
708
+ session: plainResponse.session,
709
+ credential: credentialPromise,
710
+ // Re-trigger credential request
711
+ trigger: () => __awaiter(this, void 0, void 0, function* () {
712
+ const vpToken = yield enhancedTriggerTS43();
713
+ // Update the credential promise
714
+ credentialPromise = Promise.resolve({
715
+ credential: typeof vpToken === 'string' ? vpToken : Object.values(vpToken)[0],
716
+ session: plainResponse.session,
717
+ authenticated: true
718
+ });
719
+ }),
720
+ cancel: () => {
721
+ // TS43 doesn't have a way to cancel once triggered
722
+ // but we can reject the promise
723
+ credentialPromise = Promise.reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, 'Authentication cancelled'));
724
+ }
706
725
  };
707
726
  }
708
727
  else {
709
- // In UI mode, directly invoke the Digital Credentials API
710
- // No modal needed - the OS provides its own UI
711
- return yield enhancedTriggerTS43();
728
+ // Standard mode - just return credential
729
+ // Wait for and return the credential
730
+ const credential = yield credentialPromise;
731
+ // Return in standard format
732
+ return {
733
+ [plainResponse.session.session_key]: credential.credential
734
+ };
712
735
  }
713
736
  }
714
737
  else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.DESKTOP) {
715
738
  // Desktop strategy - QR code based authentication
716
739
  const desktopData = plainResponse.data;
717
- // Check if headless mode is requested
718
- if (isHeadless) {
719
- // Return raw data for custom UI implementation
720
- const handler = new desktop_1.DesktopHandler();
721
- // Start polling in the background
722
- const pollingPromise = handler.invoke(plainResponse, Object.assign(Object.assign({}, desktopOptions), {
723
- // Don't pass config.endpoints.polling - let backend-provided status_url take priority
724
- pollingInterval: this.config.pollingInterval || 2000, maxPollingAttempts: this.config.maxPollingAttempts || 150,
725
- // No UI callbacks in headless mode
726
- onQRCodeReady: undefined, onStatusUpdate: undefined })).then(result => {
727
- if (result.authenticated && result.credential) {
728
- // Extract session ID from nested or flat structure
729
- let sessionId = 'default';
730
- // Check if desktopData has nested structure
731
- if (desktopData && typeof desktopData === 'object') {
732
- if (desktopData.data && typeof desktopData.data === 'object') {
733
- // Nested structure: data.data.session_id
734
- sessionId = desktopData.data.session_id || sessionId;
735
- }
736
- // Also check flat structure
737
- if (!sessionId || sessionId === 'default') {
738
- sessionId = desktopData.session_id || sessionId;
739
- }
740
- }
741
- // Return credential in expected format for subsequent API calls
742
- return {
743
- [sessionId]: result.credential
744
- };
740
+ const handler = new desktop_1.DesktopHandler();
741
+ // Extract QR code data - convert to QRCodeData format for modal
742
+ const qrCodeData = {
743
+ iosQRCode: ((_c = desktopData.data) === null || _c === void 0 ? void 0 : _c.ios_qr_image) || desktopData.ios_qr_image ||
744
+ ((_d = desktopData.data) === null || _d === void 0 ? void 0 : _d.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code || '',
745
+ androidQRCode: ((_e = desktopData.data) === null || _e === void 0 ? void 0 : _e.android_qr_image) || desktopData.android_qr_image,
746
+ iosUrl: ((_f = desktopData.data) === null || _f === void 0 ? void 0 : _f.ios_url) || desktopData.ios_url,
747
+ androidUrl: ((_g = desktopData.data) === null || _g === void 0 ? void 0 : _g.android_url) || desktopData.android_url
748
+ };
749
+ // Also keep snake_case format for extended response
750
+ const qrCodeDataSnakeCase = {
751
+ ios_qr_image: ((_h = desktopData.data) === null || _h === void 0 ? void 0 : _h.ios_qr_image) || desktopData.ios_qr_image,
752
+ android_qr_image: ((_j = desktopData.data) === null || _j === void 0 ? void 0 : _j.android_qr_image) || desktopData.android_qr_image,
753
+ qr_code: ((_k = desktopData.data) === null || _k === void 0 ? void 0 : _k.qr_code_image) || desktopData.qr_code_image || desktopData.qr_code,
754
+ challenge: (_l = desktopData.data) === null || _l === void 0 ? void 0 : _l.challenge
755
+ };
756
+ // Polling options
757
+ const pollingEndpointToUse = (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) ||
758
+ (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingEndpoint) ||
759
+ ((_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 });
761
+ // Decide whether to show modal based on preventDefaultUI
762
+ const showModal = !preventDefaultUI;
763
+ let modal;
764
+ let modalRef = undefined;
765
+ console.log('[Desktop] Modal decision:', {
766
+ preventDefaultUI,
767
+ showModal,
768
+ executionMode,
769
+ hasInvokeOptions: !!invokeOptions,
770
+ hasDesktopOptions: !!desktopOptions
771
+ });
772
+ if (showModal) {
773
+ console.log('[Desktop] Creating modal with QR data:', qrCodeData);
774
+ // Create and setup modal
775
+ modal = new modal_1.AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
776
+ modal.setCloseCallback(() => {
777
+ this.log('Desktop QR modal closed by user, cancelling polling');
778
+ handler.cancel();
779
+ });
780
+ // Add UI callbacks to polling options
781
+ pollingOptions.onQRCodeReady = (qrData) => {
782
+ console.log('[Desktop] onQRCodeReady callback triggered:', qrData);
783
+ modal.showQRCode(qrData, 'Scan with your mobile device');
784
+ };
785
+ pollingOptions.onStatusUpdate = (status) => {
786
+ if (status.status === 'pending') {
787
+ modal.updateStatus('Waiting for authentication...');
788
+ }
789
+ else if (status.status === 'authenticated') {
790
+ modal.updateStatus('Authentication successful!');
791
+ setTimeout(() => modal.close(), 1500);
745
792
  }
746
- else {
747
- throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
793
+ else if (status.status === 'expired') {
794
+ modal.updateStatus('QR code expired', true);
748
795
  }
796
+ else if (status.status === 'error') {
797
+ modal.updateStatus('Authentication failed', true);
798
+ }
799
+ };
800
+ // Note: We don't show the QR code here. It will be shown by the onQRCodeReady callback
801
+ // that gets triggered immediately when handler.invoke() is called
802
+ modalRef = modal;
803
+ }
804
+ else {
805
+ console.log('[Desktop] Modal not shown - preventDefaultUI is true');
806
+ }
807
+ // Create credential promise
808
+ const startPolling = () => handler.invoke(plainResponse, pollingOptions).then(result => {
809
+ if (result.authenticated && result.credential) {
810
+ return {
811
+ credential: result.credential,
812
+ session: plainResponse.session,
813
+ authenticated: true
814
+ };
815
+ }
816
+ else {
817
+ throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
818
+ }
819
+ });
820
+ // Handle based on execution mode
821
+ if (executionMode === 'extended') {
822
+ // Extended mode - return control methods
823
+ const credentialPromise = showModal ? startPolling() : Promise.resolve({
824
+ credential: '',
825
+ session: plainResponse.session,
826
+ authenticated: false
749
827
  });
750
828
  return {
751
- strategy: API.AUTHENTICATION_STRATEGY.DESKTOP,
752
- qrCode: (_b = desktopData.data) === null || _b === void 0 ? void 0 : _b.qr_code_image, // data.data.qr_code_image
753
- pollingPromise,
754
- session: plainResponse.session
829
+ strategy: 'desktop',
830
+ session: plainResponse.session,
831
+ credential: credentialPromise,
832
+ qr_code_data: qrCodeDataSnakeCase,
833
+ 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
755
847
  };
756
848
  }
757
849
  else {
758
- // UI mode - show QR code modal
759
- const handler = new desktop_1.DesktopHandler();
760
- const modal = new modal_1.AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
761
- // Default options with modal display
762
- const options = Object.assign(Object.assign({}, desktopOptions), {
763
- // Don't pass config.endpoints.polling - let backend-provided status_url take priority
764
- // Only explicit desktopOptions.pollingEndpoint will override backend URL
765
- pollingInterval: (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.pollingInterval) || this.config.pollingInterval || 2000, maxPollingAttempts: (desktopOptions === null || desktopOptions === void 0 ? void 0 : desktopOptions.maxPollingAttempts) || this.config.maxPollingAttempts || 150, onQRCodeReady: (qrCodeData) => {
766
- // Show QR code in modal (supports both single and dual-platform)
767
- modal.showQRCode(qrCodeData, 'Scan with your mobile device');
768
- }, onStatusUpdate: (status) => {
769
- // Update status in modal
770
- if (status.status === 'pending') {
771
- modal.updateStatus('Waiting for authentication...');
772
- }
773
- else if (status.status === 'authenticated') {
774
- modal.updateStatus('Authentication successful!');
775
- setTimeout(() => modal.close(), 1500);
776
- }
777
- else if (status.status === 'expired') {
778
- modal.updateStatus('QR code expired', true);
779
- }
780
- else if (status.status === 'error') {
781
- modal.updateStatus('Authentication failed', true);
782
- }
783
- } });
850
+ // Standard mode - return credential when complete
851
+ // Start polling and wait for result
784
852
  try {
785
- const result = yield handler.invoke(plainResponse, options);
786
- if (result.authenticated && result.credential) {
787
- // Extract session ID from nested or flat structure
788
- let sessionId = 'default';
789
- // Check if desktopData has nested structure
790
- if (desktopData && typeof desktopData === 'object') {
791
- if (desktopData.data && typeof desktopData.data === 'object') {
792
- // Nested structure: data.data.session_id
793
- sessionId = desktopData.data.session_id || sessionId;
794
- }
795
- // Also check flat structure
796
- if (!sessionId || sessionId === 'default') {
797
- sessionId = desktopData.session_id || sessionId;
798
- }
853
+ const credential = yield startPolling();
854
+ // Extract session ID for compatibility
855
+ let sessionId = 'default';
856
+ if (desktopData && typeof desktopData === 'object') {
857
+ if (desktopData.data && typeof desktopData.data === 'object') {
858
+ sessionId = desktopData.data.session_id || sessionId;
859
+ }
860
+ if (!sessionId || sessionId === 'default') {
861
+ sessionId = desktopData.session_id || sessionId;
799
862
  }
800
- // Return credential in expected format for subsequent API calls
801
- return {
802
- [sessionId]: result.credential
803
- };
804
- }
805
- else {
806
- throw this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Desktop authentication failed');
807
- }
808
- }
809
- catch (error) {
810
- if (error instanceof Error && error.name === 'PhoneAuthError') {
811
- throw error;
812
863
  }
813
- throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Desktop authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
864
+ return { [sessionId]: credential.credential };
814
865
  }
815
866
  finally {
816
- // Ensure handler cleanup
817
867
  handler.cleanup();
818
- modal.close();
868
+ if (modal)
869
+ modal.close();
819
870
  }
820
871
  }
821
872
  }
822
873
  else if (plainResponse.authentication_strategy === API.AUTHENTICATION_STRATEGY.LINK) {
823
874
  // Link strategy - app-based authentication (iOS/Android)
824
875
  const linkData = plainResponse.data;
825
- // Check if headless mode is requested
826
- if (isHeadless) {
827
- // Return raw data for custom UI implementation
828
- const handler = new link_1.LinkHandler();
829
- // Create reusable trigger function that ONLY opens the App Clip
830
- // This can be called multiple times without restarting polling
831
- const triggerLink = () => {
832
- var _a, _b;
833
- try {
834
- window.open(linkData.url, '_blank');
835
- (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
836
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
837
- url: linkData.url,
838
- success: true
839
- });
840
- }
841
- catch (error) {
842
- (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
843
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
844
- url: linkData.url,
845
- success: false,
846
- error
847
- });
848
- }
849
- };
850
- // Auto-trigger by default for Link strategy
851
- // This opens the App Clip immediately while preserving user gesture context
852
- if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
853
- triggerLink();
876
+ const handler = new link_1.LinkHandler();
877
+ // Create reusable trigger function that ONLY opens the App Clip
878
+ const triggerLink = () => {
879
+ var _a, _b;
880
+ try {
881
+ window.open(linkData.url, '_blank');
882
+ (_a = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _a === void 0 ? void 0 : _a.call(invokeOptions, {
883
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
884
+ url: linkData.url,
885
+ success: true
886
+ });
854
887
  }
855
- // Start polling in the background (independent of trigger)
856
- const pollingPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
857
- var _a;
858
- try {
859
- const result = yield handler.invoke(plainResponse, {
860
- pollingEndpoint: (_a = this.config.endpoints) === null || _a === void 0 ? void 0 : _a.polling,
861
- pollingInterval: this.config.pollingInterval || 2000,
862
- maxPollingAttempts: this.config.maxPollingAttempts || 150,
863
- // openMethod removed - always uses window.open()
864
- onLinkOpened: undefined,
865
- onStatusUpdate: undefined
866
- });
867
- if (result.authenticated && result.credential) {
868
- // Return credential in the expected format
869
- const aggregatorId = this.config.aggregatorId || 'default';
870
- resolve({ [aggregatorId]: result.credential });
871
- }
872
- else {
873
- reject(this.createError(error_utils_1.PhoneAuthErrorCode.USER_DENIED, result.error || 'Link authentication failed'));
874
- }
875
- }
876
- catch (error) {
877
- reject(error);
878
- }
879
- }));
888
+ catch (error) {
889
+ (_b = invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.onTriggerAttempt) === null || _b === void 0 ? void 0 : _b.call(invokeOptions, {
890
+ strategy: API.AUTHENTICATION_STRATEGY.LINK,
891
+ url: linkData.url,
892
+ success: false,
893
+ error
894
+ });
895
+ }
896
+ };
897
+ // Link always auto-opens the app (no SDK UI by default)
898
+ // Open immediately to preserve user gesture context
899
+ if ((invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.autoTrigger) !== false) {
900
+ triggerLink();
901
+ }
902
+ // Start polling in the background
903
+ console.log('[PhoneAuth Client] Link polling config:', {
904
+ fromInvokeOptions: invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint,
905
+ fromClientConfig: (_o = this.config.endpoints) === null || _o === void 0 ? void 0 : _o.polling,
906
+ finalPollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_p = this.config.endpoints) === null || _p === void 0 ? void 0 : _p.polling)
907
+ });
908
+ const pollingOptions = {
909
+ pollingEndpoint: (invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.pollingEndpoint) || ((_q = this.config.endpoints) === null || _q === void 0 ? void 0 : _q.polling),
910
+ 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,
912
+ onLinkOpened: undefined,
913
+ onStatusUpdate: undefined
914
+ };
915
+ 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
+ // Handle based on execution mode
930
+ if (executionMode === 'extended') {
931
+ // Extended mode - return control methods
880
932
  return {
881
- strategy: API.AUTHENTICATION_STRATEGY.LINK,
882
- url: linkData.url,
883
- pollingPromise,
933
+ strategy: 'link',
884
934
  session: plainResponse.session,
885
- // Trigger function to open App Clip (can be called multiple times)
886
- // Does NOT restart polling - just opens the URL
887
- trigger: triggerLink
888
- };
889
- }
890
- else {
891
- // UI mode - show button to open app link
892
- const modal = new modal_1.AuthModal(invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.modalOptions, invokeOptions === null || invokeOptions === void 0 ? void 0 : invokeOptions.callbacks);
893
- const handler = new link_1.LinkHandler();
894
- // Show link button in modal - user MUST click the button to open the app link
895
- // This is critical for iOS to recognize the app link (requires user interaction)
896
- yield modal.showLinkButton(linkData.url);
897
- // Link options - the app link will be opened by user clicking the modal button
898
- const options = {
899
- // Use configured polling endpoint if available
900
- pollingEndpoint: (_c = this.config.endpoints) === null || _c === void 0 ? void 0 : _c.polling,
901
- pollingInterval: this.config.pollingInterval || 2000, // Default 2 second interval
902
- maxPollingAttempts: this.config.maxPollingAttempts || 150, // Default 5 minutes total
903
- onLinkOpened: () => {
904
- this.log('Authentication app link opened');
905
- modal.updateStatus('Waiting for app authentication...');
935
+ credential: credentialPromise,
936
+ data: {
937
+ app_url: linkData.url
906
938
  },
907
- onStatusUpdate: (status) => {
908
- this.log('Link authentication status update:', status);
909
- if (status.status === 'authenticated') {
910
- modal.updateStatus('Authentication successful!');
911
- setTimeout(() => modal.close(), 1500);
912
- }
913
- else if (status.status === 'error') {
914
- modal.updateStatus('Authentication failed', true);
915
- }
939
+ // Re-open the app link
940
+ 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
+ })),
947
+ stop_polling: () => handler.cleanup(),
948
+ cancel: () => {
949
+ handler.cancel();
950
+ handler.cleanup();
916
951
  },
917
- onTimeout: () => {
918
- this.log('Link authentication timed out');
919
- modal.updateStatus('Authentication timed out', true);
920
- }
952
+ is_polling: true
921
953
  };
922
- try {
923
- const result = yield handler.invoke(plainResponse, options);
924
- if (result.authenticated && result.credential) {
925
- // Return the credential for further processing
926
- // The credential is typically the session key
927
- const aggregatorId = this.config.aggregatorId || 'default';
928
- modal.close();
929
- return { [aggregatorId]: result.credential };
930
- }
931
- else {
932
- throw this.createError(error_utils_1.PhoneAuthErrorCode.VERIFICATION_FAILED, result.error || 'Link authentication failed');
933
- }
934
- }
935
- catch (error) {
936
- modal.close();
937
- if (error instanceof Error && error.name === 'PhoneAuthError') {
938
- throw error;
939
- }
940
- throw this.createError(error_utils_1.PhoneAuthErrorCode.INTERNAL_SERVER_ERROR, `Link authentication error: ${error instanceof Error ? error.message : 'Unknown error'}`, { originalError: error });
941
- }
942
- finally {
943
- // Ensure handler cleanup
944
- handler.cleanup();
945
- }
954
+ }
955
+ else {
956
+ // Standard mode - return credential when complete
957
+ // Wait for credential and return in standard format
958
+ const credential = yield credentialPromise;
959
+ const aggregatorId = this.config.aggregatorId || 'default';
960
+ return { [aggregatorId]: credential.credential };
946
961
  }
947
962
  }
948
963
  else {
@@ -4,4 +4,4 @@ export { PhoneAuthErrorCode, isPhoneAuthError, isUserError, getUserMessage, isEr
4
4
  export type { PhoneAuthErrorCode as PhoneAuthErrorCodeType } from './error-utils';
5
5
  export { validatePhoneNumber, validatePlmn, validateUseCaseRequirements, validateConsentData, validateNonce } from './validation-utils';
6
6
  export { MobileDebugConsole } from './ui/mobile-debug-console';
7
- export { isHeadlessResult, isCredential, isLinkStrategy, isTS43Strategy, isDesktopStrategy, getStrategy, requiresPolling, requiresUserAction } from './type-guards';
7
+ export { isExtendedResponse, isCredential, isAuthCredential, isLinkStrategy, isTS43Strategy, isDesktopStrategy, getStrategy, hasPollingControls, hasTrigger, isHeadlessResult, requiresPolling, requiresUserAction } from './type-guards';